From c6c7a9d3675ec18fbdf2616dde4c4006d10ec2fa Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Thu, 21 Sep 2023 16:34:13 -0700 Subject: [PATCH 01/15] change credential functionality working Signed-off-by: wadeking98 --- .../App/components/misc/CredentialCard.tsx | 6 + .../App/components/misc/CredentialCard11.tsx | 41 +- packages/legacy/core/App/hooks/proofs.ts | 19 +- .../legacy/core/App/localization/en/index.ts | 3 +- .../legacy/core/App/localization/fr/index.ts | 3 +- .../core/App/localization/pt-br/index.ts | 3 +- .../core/App/navigators/ProofRequestStack.tsx | 2 + .../App/screens/ProofChangeCredential.tsx | 155 ++++++++ .../legacy/core/App/screens/ProofRequest.tsx | 301 +++++---------- packages/legacy/core/App/types/navigators.ts | 4 +- packages/legacy/core/App/utils/helpers.ts | 360 ++++++++++++++---- packages/oca/src/legacy/resolver/record.ts | 4 + 12 files changed, 604 insertions(+), 297 deletions(-) create mode 100644 packages/legacy/core/App/screens/ProofChangeCredential.tsx diff --git a/packages/legacy/core/App/components/misc/CredentialCard.tsx b/packages/legacy/core/App/components/misc/CredentialCard.tsx index 54738cfd35..364b855ef2 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard.tsx @@ -21,6 +21,8 @@ interface CredentialCardProps { displayItems?: (Attribute | Predicate)[] existsInWallet?: boolean satisfiedPredicates?: boolean + hasAltCredentials?: boolean + handleAltCredChange?: () => void } const CredentialCard: React.FC = ({ @@ -32,6 +34,8 @@ const CredentialCard: React.FC = ({ credName, existsInWallet, satisfiedPredicates, + hasAltCredentials, + handleAltCredChange, style = {}, onPress = undefined, }) => { @@ -50,6 +54,8 @@ const CredentialCard: React.FC = ({ credDefId={credDefId} schemaId={schemaId} credential={credential} + handleAltCredChange={handleAltCredChange} + hasAltCredentials={hasAltCredentials} proof elevated > diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index ff7e3ece49..cee325a04a 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -16,6 +16,9 @@ import { getCredentialConnectionLabel, isDataUrl } from '../../utils/helpers' import { testIdWithKey } from '../../utils/testable' import CardWatermark from './CardWatermark' +import { useNavigation } from '@react-navigation/core' +import { ProofRequestsStackParams, Screens, Stacks } from '../../types/navigators' +import { StackNavigationProp } from '@react-navigation/stack' interface CredentialCard11Props { credential?: CredentialExchangeRecord @@ -30,6 +33,8 @@ interface CredentialCard11Props { credDefId?: string schemaId?: string proof?: boolean + hasAltCredentials?: boolean + handleAltCredChange?: () => void } /* @@ -73,6 +78,8 @@ const CredentialCard11: React.FC = ({ credDefId, schemaId, proof, + hasAltCredentials, + handleAltCredChange }) => { const { width } = useWindowDimensions() const borderRadius = 10 @@ -191,6 +198,22 @@ const CredentialCard11: React.FC = ({ fontSize: 22, transform: [{ rotate: '-30deg' }], }, + selectedCred: { + borderWidth: 5, + borderRadius: 15, + borderColor: ColorPallet.semantic.focus + }, + seperator: { + width: "100%", + height: 2, + marginVertical: 10, + backgroundColor: ColorPallet.grayscale.lightGrey + }, + credActionText: { + fontSize: 20, + fontWeight: 'bold', + color: ColorPallet.brand.link + } }) const parseAttribute = (item: (Attribute & Predicate) | undefined) => { @@ -422,6 +445,19 @@ const CredentialCard11: React.FC = ({ renderItem={({ item }) => { return renderCardAttribute(item as Attribute & Predicate) }} + ListFooterComponent={ + hasAltCredentials ? ( + + + + + Change credential + + + + + ) : null + } /> @@ -512,8 +548,7 @@ const CredentialCard11: React.FC = ({ style={styles.cardContainer} accessible={true} accessibilityLabel={ - `${overlay.metaOverlay?.issuer ? `${t('Credentials.IssuedBy')} ${overlay.metaOverlay?.issuer}` : ''}, ${ - overlay.metaOverlay?.watermark ?? '' + `${overlay.metaOverlay?.issuer ? `${t('Credentials.IssuedBy')} ${overlay.metaOverlay?.issuer}` : ''}, ${overlay.metaOverlay?.watermark ?? '' } ${overlay.metaOverlay?.name ?? ''} ${t('Credentials.Credential')}.` + cardData.map((item) => { const { label, value } = parseAttribute(item as (Attribute & Predicate) | undefined) @@ -532,7 +567,7 @@ const CredentialCard11: React.FC = ({ } return overlay.bundle ? ( { setDimensions({ cardHeight: event.nativeEvent.layout.height, cardWidth: event.nativeEvent.layout.width }) }} diff --git a/packages/legacy/core/App/hooks/proofs.ts b/packages/legacy/core/App/hooks/proofs.ts index 9c4b7ae294..be44e16fb9 100644 --- a/packages/legacy/core/App/hooks/proofs.ts +++ b/packages/legacy/core/App/hooks/proofs.ts @@ -1,6 +1,8 @@ import { ProofExchangeRecord } from '@aries-framework/core' -import { useProofs } from '@aries-framework/react-hooks' +import { useAgent, useCredentials, useProofById, useProofs } from '@aries-framework/react-hooks' import { useMemo } from 'react' +import { retrieveCredentialsForProof } from '../utils/helpers' +import { useTranslation } from 'react-i18next' export const useProofsByConnectionId = (connectionId: string): ProofExchangeRecord[] => { const { records: proofs } = useProofs() @@ -9,3 +11,18 @@ export const useProofsByConnectionId = (connectionId: string): ProofExchangeReco [proofs, connectionId] ) } + +export const getAllCredentialsForProof = (proofId: string) => { + const { t } = useTranslation() + const { agent } = useAgent() + const fullCredentials = useCredentials().records + const proof = useProofById(proofId) + return useMemo( + () => { + if (!proof || !agent) { + return + } + return retrieveCredentialsForProof(agent, proof, fullCredentials, t, true) + }, [proofId] + ) +} diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index d921fc2583..18ee81c70c 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -166,7 +166,6 @@ const translation = { "AllowCameraUse": "Allow camera use", "CameraDisclosure": "The camera is used to scan QR codes that initiate a credential offer or credential request. No information about the images is stored, used for analytics, or shared.", "ToContinueUsing": "To continue using the Aries Bifold scan feature, please allow camera permissions.", - "Allow": "Allow", "OpenSettings": "Open settings", }, "PINCreate": { @@ -510,6 +509,7 @@ const translation = { "CredentialDetails": "Credential Details", "Notifications": "Notifications", "CredentialOffer": "Credential Offer", + "ProofChangeCredential":"Choose a credential", "ProofRequest": "Proof Request", "ProofRequestDetails": "Proof Request Details", "ProofRequestAttributeDetails": "Proof Request Attribute Details", @@ -546,6 +546,7 @@ const translation = { "EnableWalletNaming": "Enable wallet naming?", "ToggleWalletNaming": "Toggle wallet naming", "NameYourWallet": "Name your wallet", + "EditWalletName": "Edit wallet name", "ThisIsTheName": "This is the name people see when connecting with you.", "CharCountTitle": "Character count exceeded", "CharCountDescription": "You've exceeded the maximum character count of 50 characters. Please reduce your character count.", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index 3a7d19f1eb..d548f6a701 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -166,7 +166,6 @@ const translation = { "AllowCameraUse": "Autoriser l'utilisation de la caméra", "CameraDisclosure": "La caméra est utilisée pour capturer les codes QR qui initient une offre de justificatifs d'identité ou une demande de preuve. Aucune information de l'image n'est sauvegardée, utilisée pour les analyses, ou partagée.", "ToContinueUsing": "Pour continuer à utiliser la fonction de balayage du Portefeuille QC, autoriser l'utilisation de caméra.", - "Allow": "Autoriser", "OpenSettings": "Ourvir Paramètres", }, "PINCreate": { @@ -499,6 +498,7 @@ const translation = { "CredentialDetails": "Détails des justificatifs", "Notifications": "Notifications", "CredentialOffer": "Offre de justificatif", + "ProofChangeCredential":"Choose a credential (FR)", "ProofRequest": "Demande de preuve", "ProofRequestAttributeDetails": "Détails des attributs de la demande de preuve", "ProofDetails": "Détails de la preuve", @@ -535,6 +535,7 @@ const translation = { "EnableWalletNaming": "Enable wallet naming? (FR)", "ToggleWalletNaming": "Toggle wallet naming (FR)", "NameYourWallet": "Name your wallet (FR)", + "EditWalletName": "Edit wallet name (FR)", "ThisIsTheName": "This is the name people see when connecting with you. (FR)", "CharCountTitle": "Character count exceeded (FR)", "CharCountDescription": "You've exceeded the maximum character count of 50 characters. Please reduce your character count. (FR)", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index c9e8888f4f..6b83fd596c 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -160,7 +160,6 @@ const translation = { "AllowCameraUse": "Permitir uso da câmera", "CameraDisclosure": "A câmera é usada para escanear QR Codes para processamento imediato no dispositivo. Nenhuma informação sobre as imagens é armazenada, usada para análise, ou compartilhada.", "ToContinueUsing": "Para continuar usando a funcionalidade de scan da Aries Bifold, favor permitir o uso da câmera.", - "Allow": "Permitir", "OpenSettings": "Abrir as configurações", }, "PINCreate": { @@ -483,6 +482,7 @@ const translation = { "Notifications": "Notificações", "CredentialOffer": "Oferta de Credencial", "ProofRequest": "Requisição de Prova", + "ProofChangeCredential":"Choose a credential (PB)", "ProofRequestDetails": "Detalhes Da Solicitação De Comprovação", "ProofRequestAttributeDetails": "Atributos de Requisição de Prova", "ProofDetails": "Detalhes da prova", @@ -511,6 +511,7 @@ const translation = { "EnableWalletNaming": "Habilitar nome da carteira?", "ToggleWalletNaming": "Alternar nome da carteira", "NameYourWallet": "Nome da sua carteira", + "EditWalletName": "Edit wallet name (PT-BR)", "ThisIsTheName": "Este é o nome que as pessoas verão quando se conectar a você.", "CharCountTitle": "Número de caracteres excedido", "CharCountDescription": "Voce excedeu o numer máximo de 50 caracteres. Por favor, reduza o número de caracteres.", diff --git a/packages/legacy/core/App/navigators/ProofRequestStack.tsx b/packages/legacy/core/App/navigators/ProofRequestStack.tsx index b169f2a6ce..42efc7933a 100644 --- a/packages/legacy/core/App/navigators/ProofRequestStack.tsx +++ b/packages/legacy/core/App/navigators/ProofRequestStack.tsx @@ -11,6 +11,7 @@ import ProofRequestDetails from '../screens/ProofRequestDetails' import ProofRequestUsageHistory from '../screens/ProofRequestUsageHistory' import ProofRequesting from '../screens/ProofRequesting' import { ProofRequestsStackParams, Screens } from '../types/navigators' +import ProofChangeCredential from '../screens/ProofChangeCredential' import { testIdWithKey } from '../utils/testable' import { createDefaultStackOptions } from './defaultStackOptions' @@ -35,6 +36,7 @@ const ProofRequestStack: React.FC = () => { title: '', })} /> + + +const ProofChangeCredential: React.FC = ({ route, navigation }) => { + if (!route?.params) { + throw new Error('Change credential route params were not set properly') + } + const proofId = route.params.proofId + const fullCredentials = useCredentials().records + const altCredentials = route.params.altCredentials + const selectedCredentials = route.params.selectedCredentials + const proof = useProofById(proofId) + const { ColorPallet, TextTheme } = useTheme() + const { t } = useTranslation() + const { agent } = useAgent() + const [loading, setLoading] = useState(false) + const [proofItems, setProofItems] = useState([]) + const [retrievedCredentials, setRetrievedCredentials] = useState() + const [selectedCred, setSelectedCred] = useState('') + const credProofPromise = getAllCredentialsForProof(proofId) + const styles = StyleSheet.create({ + pageContainer: { + flex: 1, + }, + pageMargin: { + marginHorizontal: 20, + }, + cardLoading: { + backgroundColor: ColorPallet.brand.secondaryBackground, + flex: 1, + flexGrow: 1, + marginVertical: 35, + borderRadius: 15, + paddingHorizontal: 10, + }, + selectedCred: { + borderWidth: 5, + borderRadius: 15, + borderColor: ColorPallet.semantic.focus, + }, + }) + + useEffect(() => { + if (proofItems) { + const displayedCredIds: string[] = proofItems.map((item) => item.credId) + setSelectedCred(displayedCredIds.find((id) => selectedCredentials.includes(id)) ?? '') + } + }, [proofItems]) + + const getCredentialsFields = (): Fields => ({ + ...retrievedCredentials?.attributes, + ...retrievedCredentials?.predicates, + }) + + useEffect(() => { + + setLoading(true) + + credProofPromise?.then((value) => { + if (value) { + const { groupedProof, retrievedCredentials } = value + setLoading(false) + setRetrievedCredentials(retrievedCredentials) + setProofItems(groupedProof.filter((proof) => altCredentials.includes(proof.credId))) + } + }) + .catch((err: unknown) => { + const error = new BifoldError( + t('Error.Title1026'), + t('Error.Message1026'), + (err as Error)?.message ?? err, + 1026 + ) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + }) + }, []) + + const listHeader = () => { + return ( + + {loading ? ( + + + + ) : ( + You have multiple credentials to choose from: + )} + + ) + } + + const changeCred = (credId: string, altCreds: string[]) => { + const newSelectedCreds = selectedCredentials.filter(id => !altCreds.includes(id)) + navigation.getParent()?.navigate(Stacks.NotificationStack, { + screen: Screens.ProofRequest, + params: { + proofId, + selectedCredentials: [credId, ...newSelectedCreds], + }, + }) + } + + return ( + + { + return ( + + changeCred(item.credId ?? '', item.altCredentials)} + style={[item.credId === selectedCred ? styles.selectedCred : undefined, { marginBottom: 10 }]} + > + + + + ) + }} + > + + ) +} + +export default ProofChangeCredential diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 6bdf8c4e58..2982bf75e4 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -13,7 +13,7 @@ import { Predicate, ProofCredentialItems } from '@hyperledger/aries-oca/build/le import { useIsFocused } from '@react-navigation/core' import React, { useEffect, useMemo, useState } from 'react' import { useTranslation } from 'react-i18next' -import { DeviceEventEmitter, FlatList, StyleSheet, Text, View } from 'react-native' +import { DeviceEventEmitter, FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Icon from 'react-native-vector-icons/MaterialIcons' @@ -32,29 +32,28 @@ import { useTheme } from '../contexts/theme' import { useTour } from '../contexts/tour/tour-context' import { useOutOfBandByConnectionId } from '../hooks/connections' import { BifoldError } from '../types/error' -import { NotificationStackParams, Screens, TabStacks } from '../types/navigators' +import { NotificationStackParams, Screens, Stacks, TabStacks } from '../types/navigators' import { ModalUsage } from '../types/remove' import { TourID } from '../types/tour' import { useAppAgent } from '../utils/agent' import { getCredentialIdentifiers } from '../utils/credential' -import { mergeAttributesAndPredicates, processProofAttributes, processProofPredicates } from '../utils/helpers' +import { Fields, evaluatePredicates, getCredentialInfo, mergeAttributesAndPredicates, processProofAttributes, processProofPredicates, retrieveCredentialsForProof } from '../utils/helpers' import { testIdWithKey } from '../utils/testable' import ProofRequestAccept from './ProofRequestAccept' +import { getAllCredentialsForProof } from '../hooks/proofs' type ProofRequestProps = StackScreenProps -type Fields = Record const ProofRequest: React.FC = ({ navigation, route }) => { if (!route?.params) { throw new Error('ProofRequest route prams were not set properly') } - const { proofId } = route?.params + const { proofId, selectedCredentials } = route?.params const { agent } = useAppAgent() const { t } = useTranslation() const { assertConnectedNetwork } = useNetwork() - const fullCredentials = useCredentials().records const proof = useProofById(proofId) const connection = proof?.connectionId ? useConnectionById(proof.connectionId) : undefined const proofConnectionLabel = connection?.theirLabel ?? proof?.connectionId ?? '' @@ -68,10 +67,13 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const goalCode = useOutOfBandByConnectionId(proof?.connectionId ?? '')?.outOfBandInvitation.goalCode const { enableTours: enableToursConfig, OCABundleResolver } = useConfiguration() const [containsPI, setContainsPI] = useState(false) + const [activeCreds, setActiveCreds] = useState([]) const [store, dispatch] = useStore() + const credProofPromise = getAllCredentialsForProof(proofId) const { start } = useTour() const screenIsFocused = useIsFocused() + const styles = StyleSheet.create({ pageContainer: { flex: 1, @@ -154,101 +156,41 @@ const ProofRequest: React.FC = ({ navigation, route }) => { } }, []) - useMemo(() => { - if (!(agent && proof)) { - return - } - - setLoading(true) - const retrieveCredentialsForProof = async (proof: ProofExchangeRecord) => { - try { - const format = await agent.proofs.getFormatData(proof.id) - const hasAnonCreds = format.request?.anoncreds !== undefined - const hasIndy = format.request?.indy !== undefined - const credentials = await agent.proofs.getCredentialsForRequest({ - proofRecordId: proof.id, - proofFormats: { - // FIXME: AFJ will try to use the format, even if the value is undefined (but the key is present) - // We should ignore the key, if the value is undefined. For now this is a workaround. - ...(hasIndy - ? { - indy: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } - : {}), - - ...(hasAnonCreds - ? { - anoncreds: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } - : {}), - }, - }) - if (!credentials) { - throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) - } + useEffect(() => { - if (!format) { - throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) + setLoading(true) + credProofPromise?.then((value) => { + if (value) { + const { groupedProof, retrievedCredentials } = value + setLoading(false) + setRetrievedCredentials(retrievedCredentials) + let credList: string[] = [] + if (selectedCredentials) { + credList = selectedCredentials + } else { + // we only want one of each satisfying credential + groupedProof.forEach(item => { + const credId = item.altCredentials[0] + if (!credList.includes(credId)) { + credList.push(credId) + } + }) } - - return { format, credentials } - } catch (err: unknown) { - const error = new BifoldError( - t('Error.Title1043'), - t('Error.Message1043'), - (err as Error)?.message ?? err, - 1043 - ) - DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + setActiveCreds(groupedProof.filter(item => credList.includes(item.credId))) + setProofItems(groupedProof) } - } - - retrieveCredentialsForProof(proof) - .then((retrieved) => retrieved ?? { format: undefined, credentials: undefined }) - .then(({ format, credentials }) => { - if (!(format && credentials && fullCredentials)) { - return - } + }).catch((err: unknown) => { + const error = new BifoldError( + t('Error.Title1026'), + t('Error.Message1026'), + (err as Error)?.message ?? err, + 1026 + ) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + }) - const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy - const reqCredIds = [ - ...Object.keys(proofFormat?.attributes ?? {}).map((key) => proofFormat?.attributes[key][0]?.credentialId), - ...Object.keys(proofFormat?.predicates ?? {}).map((key) => proofFormat?.predicates[key][0]?.credentialId), - ] - const credentialRecords = fullCredentials.filter((record) => - reqCredIds.includes(record.credentials[0]?.credentialRecordId) - ) - const attributes = processProofAttributes(format.request, credentials, credentialRecords) - const predicates = processProofPredicates(format.request, credentials, credentialRecords) - setRetrievedCredentials(proofFormat) - - const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) - setProofItems(groupedProof) - setLoading(false) - }) - .catch((err: unknown) => { - const error = new BifoldError( - t('Error.Title1026'), - t('Error.Message1026'), - (err as Error)?.message ?? err, - 1026 - ) - DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) - }) - }, []) + }, [selectedCredentials]) const toggleDeclineModalVisible = () => setDeclineModalVisible(!declineModalVisible) @@ -259,112 +201,35 @@ const ProofRequest: React.FC = ({ navigation, route }) => { useEffect(() => { // get oca bundle to see if we're presenting personally identifiable elements - proofItems.some(async (item) => { - if (!item || !item.credExchangeRecord) { + activeCreds.some(async (item) => { + if (!item || !(item.credDefId || item.schemaId)) { return false } const labels = (item.attributes ?? []).map((field) => field.label ?? field.name ?? '') - const credIds = getCredentialIdentifiers(item.credExchangeRecord) - const bundle = await OCABundleResolver.resolveAllBundles({ identifiers: credIds }) + const bundle = await OCABundleResolver.resolveAllBundles({ identifiers: { credentialDefinitionId: item.credDefId, schemaId: item.schemaId } }) const flaggedAttributes: string[] = (bundle as any).bundle.bundle.flaggedAttributes.map((attr: any) => attr.name) const foundPI = labels.some((label) => flaggedAttributes.includes(label)) setContainsPI(foundPI) return foundPI }) - }, [proofItems]) - - /** - * Retrieve current credentials info filtered by `credentialDefinitionId` if given. - * @param credDefId Credential Definition Id - * @returns Array of `AnonCredsCredentialInfo` - */ - const getCredentialInfo = (credDefId?: string): AnonCredsCredentialInfo[] => { - const credentialInfo: AnonCredsCredentialInfo[] = [] - const fields = getCredentialsFields() - - Object.keys(fields).forEach((proofKey) => { - credentialInfo.push(...fields[proofKey].map((attr) => attr.credentialInfo)) - }) + }, [activeCreds]) - return credDefId == undefined - ? credentialInfo - : credentialInfo.filter((cred) => cred.credentialDefinitionId === credDefId) - } - - /** - * Evaluate if given attribute value satisfies the predicate. - * @param attribute Credential attribute value - * @param pValue Predicate value - * @param pType Predicate type ({@link AnonCredsPredicateType}) - * @returns `true`if predicate is satisfied, otherwise `false` - */ - const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPredicateType): boolean => { - if (pType === '>=') { - return attribute >= pValue - } - - if (pType === '>') { - return attribute > pValue - } - - if (pType === '<=') { - return attribute <= pValue - } - if (pType === '<') { - return attribute < pValue - } - - return false - } - - /** - * Given proof credential items, evaluate and return its predicates, setting `satisfied` property. - * @param proofCredentialsItems - * @returns Array of evaluated predicates - */ - const evaluatePredicates = - (credDefId?: string) => - (proofCredentialItems: ProofCredentialItems): Predicate[] => { - const predicates = proofCredentialItems.predicates - - if (!predicates || predicates.length == 0) { - return [] - } - if (credDefId && credDefId != proofCredentialItems.credDefId) { - return [] - } - - const credentialAttributes = getCredentialInfo(proofCredentialItems.credDefId).map((ci) => ci.attributes) - - return predicates.map((predicate) => { - const { pType: pType, pValue: pValue, name: field } = predicate - let satisfied = false - if (field) { - const attribute = (credentialAttributes.find((attr) => attr[field] != undefined) ?? {})[field] - - if (attribute && pValue) { - satisfied = evaluateOperation(Number(attribute), Number(pValue), pType as AnonCredsPredicateType) - } - } - return { ...predicate, satisfied } - }) - } const hasAvailableCredentials = (credDefId?: string): boolean => { const fields = getCredentialsFields() if (credDefId) { - return getCredentialInfo(credDefId).some((credInfo) => credInfo.credentialDefinitionId === credDefId) + return getCredentialInfo(credDefId, fields).some((credInfo) => credInfo.credentialDefinitionId === credDefId) } - return !!retrievedCredentials && Object.values(fields).every((c) => c.length > 0) + return !!retrievedCredentials && Object.values(fields).every((c) => c?.length > 0) } - const hasSatisfiedPredicates = (credDefId?: string) => - proofItems.flatMap(evaluatePredicates(credDefId)).every((p) => p.satisfied) + const hasSatisfiedPredicates = (fields: Fields, credDefId?: string) => + activeCreds.flatMap(evaluatePredicates(fields, credDefId)).every((p) => p.satisfied) const handleAcceptPress = async () => { try { @@ -380,14 +245,27 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const formatToUse = format.request?.anoncreds ? 'anoncreds' : 'indy' - const automaticRequestedCreds = - retrievedCredentials && - (await agent.proofs.selectCredentialsForRequest({ - proofRecordId: proof.id, - proofFormats: { - [formatToUse]: {}, - }, - })) + // this is the best way to supply our desired credentials in the proof, otherwise it selects them automatically + const credObject = { + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes) + .map(key => { + return { + [key]: retrievedCredentials.attributes[key] + .find(cred => activeCreds.map(item => item.credId).includes(cred.credentialId)) + } + }).reduce((prev, current) => { return { ...prev, ...current } }), + predicates: Object.keys(retrievedCredentials.predicates) + .map(key => { + return { + [key]: retrievedCredentials.predicates[key] + .find(cred => activeCreds.map(item => item.credId).includes(cred.credentialId)) + } + }).reduce((prev, current) => { return { ...prev, ...current } }, {}), + selfAttestedAttributes: {} + } + const automaticRequestedCreds = { proofFormats: { [formatToUse]: { ...credObject } } } + if (!automaticRequestedCreds) { throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) @@ -440,7 +318,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { <> - {!hasAvailableCredentials() || !hasSatisfiedPredicates() ? ( + {!hasAvailableCredentials() || !hasSatisfiedPredicates(getCredentialsFields()) ? ( = ({ navigation, route }) => { color={ListItems.proofIcon.color} size={ListItems.proofIcon.fontSize} /> - {hasSatisfiedPredicates() ? ( + {hasSatisfiedPredicates(getCredentialsFields()) ? ( {proofConnectionLabel || t('ContactDetails.AContact')}{' '} {t('ProofRequest.IsRequestingSomethingYouDontHaveAvailable')} @@ -464,8 +342,8 @@ const ProofRequest: React.FC = ({ navigation, route }) => { {proofConnectionLabel || t('ContactDetails.AContact')}{' '} {t('ProofRequest.IsRequestingYouToShare')} - {` ${proofItems.length} `} - {proofItems.length > 1 ? t('ProofRequest.Credentials') : t('ProofRequest.Credential')} + {` ${activeCreds?.length} `} + {activeCreds?.length > 1 ? t('ProofRequest.Credentials') : t('ProofRequest.Credential')} )} {containsPI && ( @@ -506,6 +384,10 @@ const ProofRequest: React.FC = ({ navigation, route }) => { ) } + const handleAltCredChange = (altCredentials: string[]) => { + navigation.getParent()?.navigate(Stacks.ProofRequestsStack, { screen: Screens.ProofChangeCredential, params: { altCredentials, proofId, selectedCredentials: selectedCredentials ?? activeCreds.map(item => item.credId) } }) + } + const proofPageFooter = () => { return ( @@ -519,7 +401,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { testID={testIdWithKey('Share')} buttonType={ButtonType.Primary} onPress={handleAcceptPress} - disabled={!hasAvailableCredentials() || !hasSatisfiedPredicates()} + disabled={!hasAvailableCredentials() || !hasSatisfiedPredicates(getCredentialsFields())} /> @@ -534,26 +416,33 @@ const ProofRequest: React.FC = ({ navigation, route }) => { ) } + // FIXME: (WK) we need to have all creds in the cred list otherwise react will complain that the order of hooks changes. Solution it to add all creds to flatlist but only display selection return ( { return ( - - + + {loading || !activeCreds.map(cred => cred.credId).includes(item.credId) ? null : ( + + 1} + handleAltCredChange={(item.altCredentials && item.altCredentials.length > 1) ? () => { handleAltCredChange(item.altCredentials, item.credId) } : undefined} + proof={true} + > + + )} ) }} diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index c07889eb83..7a6bc014bc 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -30,6 +30,7 @@ export enum Screens { UseBiometry = 'Use Biometry', Developer = 'Developer', CustomNotification = 'Custom Notification', + ProofChangeCredential = 'Choose a credential', ProofRequests = 'Proof Requests', ProofRequesting = 'Proof Requesting', ProofDetails = 'Proof Details', @@ -103,6 +104,7 @@ export type ProofRequestsStackParams = { [Screens.ProofDetails]: { recordId: string; isHistory?: boolean; senderReview?: boolean } [Screens.ProofRequestDetails]: { templateId: string; connectionId?: string } [Screens.ProofRequestUsageHistory]: { templateId: string } + [Screens.ProofChangeCredential]: { altCredentials: string[], proofId: string, selectedCredentials: string[] } } export type CredentialStackParams = { @@ -135,7 +137,7 @@ export type SettingStackParams = { export type NotificationStackParams = { [Screens.CredentialDetails]: { credentialId: string } [Screens.CredentialOffer]: { credentialId: string } - [Screens.ProofRequest]: { proofId: string } + [Screens.ProofRequest]: { proofId: string, selectedCredentials?: string[] } [Screens.CustomNotification]: undefined [Screens.ProofDetails]: { recordId: string } } diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 98d894f75f..c992e5185f 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -1,5 +1,7 @@ import { + AnonCredsCredentialInfo, AnonCredsCredentialsForProofRequest, + AnonCredsPredicateType, AnonCredsProofFormat, AnonCredsProofFormatService, AnonCredsProofRequestRestriction, @@ -27,6 +29,7 @@ import { Attribute, Predicate, ProofCredentialAttributes, + ProofCredentialItems, ProofCredentialPredicates, } from '@hyperledger/aries-oca/build/legacy' import { Buffer } from 'buffer' @@ -34,13 +37,17 @@ import moment from 'moment' import { ParsedUrl, parseUrl } from 'query-string' import { Dispatch, ReactNode, SetStateAction } from 'react' -import { domain } from '../constants' +import { EventTypes, domain } from '../constants' import { i18n } from '../localization/index' import { Role } from '../types/chat' import { ChildFn } from '../types/tour' export { parsedCredDefNameFromCredential } from './cred-def' import { parseCredDefFromId } from './cred-def' +import { BifoldAgent } from './agent' +import { BifoldError } from '../types/error' +import { DeviceEventEmitter } from 'react-native' +import { TFunction } from 'react-i18next' export { parsedCredDefName } from './cred-def' export { parsedSchema } from './schema' @@ -176,8 +183,8 @@ export function formatTime( ? momentTime.format(formatString) : // if non-english, don't include comma between year and month isNonEnglish - ? `${momentTime.format(formatString)} ${momentTime.format('YYYY')}` - : `${momentTime.format(formatString)}, ${momentTime.format('YYYY')}` + ? `${momentTime.format(formatString)} ${momentTime.format('YYYY')}` + : `${momentTime.format(formatString)}, ${momentTime.format('YYYY')}` if (includeHour) { formattedTime = `${formattedTime}, ${momentTime.format('h:mm a')}` } @@ -331,11 +338,159 @@ const credNameFromRestriction = (queries?: AnonCredsProofRequestRestriction[]): export const isDataUrl = (value: string | number | null) => { return typeof value === 'string' && value.startsWith('data:image/') } +export type Fields = Record + +/** + * Retrieve current credentials info filtered by `credentialDefinitionId` if given. + * @param credDefId Credential Definition Id + * @returns Array of `AnonCredsCredentialInfo` + */ +export const getCredentialInfo = (credDefId: string, fields: Fields): AnonCredsCredentialInfo[] => { + const credentialInfo: AnonCredsCredentialInfo[] = [] + + Object.keys(fields).forEach((proofKey) => { + credentialInfo.push(...fields[proofKey].map((attr) => attr.credentialInfo)) + }) + + return credDefId == undefined + ? credentialInfo + : credentialInfo.filter((cred) => cred.credentialDefinitionId === credDefId) +} + +/** + * Evaluate if given attribute value satisfies the predicate. + * @param attribute Credential attribute value + * @param pValue Predicate value + * @param pType Predicate type ({@link AnonCredsPredicateType}) + * @returns `true`if predicate is satisfied, otherwise `false` + */ +const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPredicateType): boolean => { + if (pType === '>=') { + return attribute >= pValue + } + + if (pType === '>') { + return attribute > pValue + } + + if (pType === '<=') { + return attribute <= pValue + } + if (pType === '<') { + return attribute < pValue + } + + return false +} + +/** + * Given proof credential items, evaluate and return its predicates, setting `satisfied` property. + * @param proofCredentialsItems + * @returns Array of evaluated predicates + */ +export const evaluatePredicates = + (fields: Fields, credDefId?: string) => + (proofCredentialItems: ProofCredentialItems): Predicate[] => { + const predicates = proofCredentialItems.predicates + + if (!predicates || predicates.length == 0) { + return [] + } + + if (credDefId && credDefId != proofCredentialItems.credDefId || !proofCredentialItems.credDefId) { + return [] + } + + const credentialAttributes = getCredentialInfo(proofCredentialItems.credDefId, fields).map((ci) => ci.attributes) + + return predicates.map((predicate) => { + const { pType: pType, pValue: pValue, name: field } = predicate + let satisfied = false + + if (field) { + const attribute = (credentialAttributes.find((attr) => attr[field] != undefined) ?? {})[field] + + if (attribute && pValue) { + satisfied = evaluateOperation(Number(attribute), Number(pValue), pType as AnonCredsPredicateType) + } + } + + return { ...predicate, satisfied } + }) + } + + +export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: ProofExchangeRecord, fullCredentials: CredentialExchangeRecord[], t: TFunction<"translation", undefined>, allCreds?: boolean, credPreference?: string[]) => { + try { + const format = await agent.proofs.getFormatData(proof.id) + const hasAnonCreds = format.request?.anoncreds !== undefined + const hasIndy = format.request?.indy !== undefined + const credentials = await agent.proofs.getCredentialsForRequest({ + proofRecordId: proof.id, + proofFormats: { + // FIXME: AFJ will try to use the format, even if the value is undefined (but the key is present) + // We should ignore the key, if the value is undefined. For now this is a workaround. + ...(hasIndy + ? { + indy: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } + : {}), + + ...(hasAnonCreds + ? { + anoncreds: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } + : {}), + }, + }) + if (!credentials) { + throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) + } + + if (!format) { + throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) + } + + if (!(format && credentials && fullCredentials)) { + return + } + + const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy + + const attributes = processProofAttributes(format.request, credentials, fullCredentials, allCreds, credPreference) + const predicates = processProofPredicates(format.request, credentials, fullCredentials, allCreds, credPreference) + + const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) + return { groupedProof: groupedProof, retrievedCredentials: proofFormat } + } catch (err: unknown) { + const error = new BifoldError( + t('Error.Title1043'), + t('Error.Message1043'), + (err as Error)?.message ?? err, + 1043 + ) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + } +} export const processProofAttributes = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, - credentialRecords?: CredentialExchangeRecord[] + credentialRecords?: CredentialExchangeRecord[], + allCreds?: boolean, + credPreference?: string[] ): { [key: string]: ProofCredentialAttributes } => { const processedAttributes = {} as { [key: string]: ProofCredentialAttributes } @@ -348,50 +503,69 @@ export const processProofAttributes = ( } for (const key of Object.keys(retrievedCredentialAttributes)) { - // The shift operation modifies the original input array, therefore make a copy - const credential = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn).shift() - const credNameRestriction = credNameFromRestriction(requestedProofAttributes[key]?.restrictions) - - let credName = credNameRestriction ?? key - if (credential?.credentialInfo?.credentialDefinitionId || credential?.credentialInfo?.schemaId) { - credName = parseCredDefFromId( - credential?.credentialInfo?.credentialDefinitionId, - credential?.credentialInfo?.schemaId - ) - } - let revoked = false - let credExchangeRecord = undefined - if (credential) { - credExchangeRecord = credentialRecords?.filter( - (record) => record.credentials[0]?.credentialRecordId === credential.credentialId - )[0] - revoked = credExchangeRecord?.revocationNotification !== undefined + const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) + + let credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) + if (!allCreds) { + let cred = undefined + if (credPreference) { + cred = credentialList.find(cred => credPreference.includes(cred.credId)) ?? credentialList.shift() + } else { + cred = credentialList.shift() + } + credentialList = cred ? [cred] : [] as AnonCredsRequestedAttributeMatch[] } - const { name, names } = requestedProofAttributes[key] + //iterate over all credentials that satisfy the proof + for (const credential of credentialList) { - for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { - if (!processedAttributes[credName]) { - // init processedAttributes object - processedAttributes[credName] = { - credExchangeRecord, - schemaId: credential?.credentialInfo?.schemaId, - credDefId: credential?.credentialInfo?.credentialDefinitionId, - credName, - attributes: [], - } - } + const credNameRestriction = credNameFromRestriction(requestedProofAttributes[key]?.restrictions) - let attributeValue = '' + let credName = credNameRestriction ?? key + if (credential?.credentialInfo?.credentialDefinitionId || credential?.credentialInfo?.schemaId) { + credName = parseCredDefFromId( + credential?.credentialInfo?.credentialDefinitionId, + credential?.credentialInfo?.schemaId + ) + } + let revoked = false + let credExchangeRecord = undefined if (credential) { - attributeValue = credential.credentialInfo.attributes[attributeName] + credExchangeRecord = credentialRecords?.find( + (record) => record.credentials.map(cred=>cred.credentialRecordId).includes(credential.credentialId) + ) + revoked = credExchangeRecord?.revocationNotification !== undefined + }else{ + continue + } + + const { name, names } = requestedProofAttributes[key] + + for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { + if (!processedAttributes[credential?.credentialId]) { + // init processedAttributes object + processedAttributes[credential.credentialId] = { + credExchangeRecord, + altCredentials, + credId: credential?.credentialId, + schemaId: credential?.credentialInfo?.schemaId, + credDefId: credential?.credentialInfo?.credentialDefinitionId, + credName, + attributes: [], + } + } + + let attributeValue = '' + if (credential) { + attributeValue = credential.credentialInfo.attributes[attributeName] + } + processedAttributes[credential.credentialId].attributes?.push( + new Attribute({ + revoked, + name: attributeName, + value: attributeValue, + }) + ) } - processedAttributes[credName].attributes?.push( - new Attribute({ - revoked, - name: attributeName, - value: attributeValue, - }) - ) } } return processedAttributes @@ -400,7 +574,9 @@ export const processProofAttributes = ( export const processProofPredicates = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, - credentialRecords?: CredentialExchangeRecord[] + credentialRecords?: CredentialExchangeRecord[], + allCreds?: boolean, + credPreference?: string[] ): { [key: string]: ProofCredentialPredicates } => { const processedPredicates = {} as { [key: string]: ProofCredentialPredicates } @@ -413,49 +589,65 @@ export const processProofPredicates = ( } for (const key of Object.keys(requestedProofPredicates)) { - // The shift operation modifies the original input array, therefore make a copy - const credential = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn).shift() - let credExchangeRecord = undefined - if (credential) { - credExchangeRecord = credentialRecords?.filter( - (record) => record.credentials[0]?.credentialRecordId === credential.credentialId - )[0] - } - const { credentialId, credentialDefinitionId, schemaId } = { ...credential, ...credential?.credentialInfo } - const revoked = - credentialRecords?.filter((record) => record.credentials[0]?.credentialRecordId === credentialId)[0] - ?.revocationNotification !== undefined - const { name, p_type: pType, p_value: pValue } = requestedProofPredicates[key] - - const credNameRestriction = credNameFromRestriction(requestedProofPredicates[key]?.restrictions) - - let credName = credNameRestriction ?? key - if (credential?.credentialInfo?.credentialDefinitionId || credential?.credentialInfo?.schemaId) { - credName = parseCredDefFromId( - credential?.credentialInfo?.credentialDefinitionId, - credential?.credentialInfo?.schemaId - ) + const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) + + let credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) + if (!allCreds) { + let cred = undefined + if (credPreference) { + cred = credentialList.find(cred => credPreference.includes(cred.credId)) ?? credentialList.shift() + } else { + cred = credentialList.shift() + } + credentialList = cred ? [cred] : [] as AnonCredsRequestedAttributeMatch[] } + for (const credential of credentialList) { - if (!processedPredicates[credName]) { - processedPredicates[credName] = { - credExchangeRecord, - schemaId, - credDefId: credentialDefinitionId, - credName: credName, - predicates: [], + let revoked = false + let credExchangeRecord = undefined + if (credential) { + credExchangeRecord = credentialRecords?.find( + (record) => record.credentials.map(cred=>cred.credentialRecordId).includes(credential.credentialId) + ) + revoked = credExchangeRecord?.revocationNotification !== undefined + }else{ + continue } - } + const { credentialId, credentialDefinitionId, schemaId } = { ...credential, ...credential?.credentialInfo } + const { name, p_type: pType, p_value: pValue } = requestedProofPredicates[key] - processedPredicates[credName].predicates?.push( - new Predicate({ - credentialId, - name, - revoked, - pValue, - pType, - }) - ) + const credNameRestriction = credNameFromRestriction(requestedProofPredicates[key]?.restrictions) + + let credName = credNameRestriction ?? key + if (credential?.credentialInfo?.credentialDefinitionId || credential?.credentialInfo?.schemaId) { + credName = parseCredDefFromId( + credential?.credentialInfo?.credentialDefinitionId, + credential?.credentialInfo?.schemaId + ) + } + + if (!processedPredicates[credential.credentialId]) { + processedPredicates[credential.credentialId] = { + altCredentials, + credExchangeRecord, + credId: credential?.credentialId, + schemaId, + credDefId: credentialDefinitionId, + credName: credName, + predicates: [], + } + } + + processedPredicates[credential.credentialId].predicates?.push( + new Predicate({ + credentialId, + name, + revoked, + pValue, + pType, + }) + ) + } } return processedPredicates } @@ -468,7 +660,9 @@ export const mergeAttributesAndPredicates = ( for (const [key, predicate] of Object.entries(predicates)) { const existingEntry = merged[key] if (existingEntry) { + const mergedAltCreds = existingEntry.altCredentials?.filter(credId => predicate.altCredentials?.includes(credId)) merged[key] = { ...existingEntry, ...predicate } + merged[key].altCredentials = mergedAltCreds } else { merged[key] = predicate } diff --git a/packages/oca/src/legacy/resolver/record.ts b/packages/oca/src/legacy/resolver/record.ts index 17c67943c2..46e08952b6 100644 --- a/packages/oca/src/legacy/resolver/record.ts +++ b/packages/oca/src/legacy/resolver/record.ts @@ -79,7 +79,9 @@ export class Predicate extends Field { } export interface ProofCredentialAttributes { + altCredentials?: string[] credExchangeRecord?: CredentialExchangeRecord + credId: string credDefId?: string schemaId?: string credName: string @@ -87,7 +89,9 @@ export interface ProofCredentialAttributes { } export interface ProofCredentialPredicates { + altCredentials?: string[] credExchangeRecord?: CredentialExchangeRecord + credId:string credDefId?: string schemaId?: string credName: string From 577093ddfe5ba60b8502be4a618f5c35e95ad76b Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Fri, 22 Sep 2023 15:25:13 -0700 Subject: [PATCH 02/15] working with predicate display Signed-off-by: wadeking98 --- packages/legacy/core/App/hooks/proofs.ts | 2 +- .../legacy/core/App/localization/en/index.ts | 3 +- .../legacy/core/App/localization/fr/index.ts | 1 + .../core/App/localization/pt-br/index.ts | 1 + .../App/screens/ProofChangeCredential.tsx | 33 ++++++++++---- .../legacy/core/App/screens/ProofRequest.tsx | 45 +++++++++++++------ packages/legacy/core/App/utils/helpers.ts | 45 +++++-------------- .../CredentialOffer.test.tsx.snap | 6 +++ 8 files changed, 79 insertions(+), 57 deletions(-) diff --git a/packages/legacy/core/App/hooks/proofs.ts b/packages/legacy/core/App/hooks/proofs.ts index be44e16fb9..9bcaefc31d 100644 --- a/packages/legacy/core/App/hooks/proofs.ts +++ b/packages/legacy/core/App/hooks/proofs.ts @@ -22,7 +22,7 @@ export const getAllCredentialsForProof = (proofId: string) => { if (!proof || !agent) { return } - return retrieveCredentialsForProof(agent, proof, fullCredentials, t, true) + return retrieveCredentialsForProof(agent, proof, fullCredentials, t) }, [proofId] ) } diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 18ee81c70c..74997f94e6 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -416,6 +416,7 @@ const translation = { "OfferDelay": "Offer delay", "RejectThisProof?": "Reject this Proof Request?", "DeclineThisProof?": "Decline this Proof Request?", + "MultipleCredentials": "You have multiple crdentials to choose from:", "AcceptingProof": "Accepting Proof", "SuccessfullyAcceptedProof": "Successfully Accepted Proof", "SensitiveInformation": "This request is asking for sensitive information.", @@ -509,7 +510,7 @@ const translation = { "CredentialDetails": "Credential Details", "Notifications": "Notifications", "CredentialOffer": "Credential Offer", - "ProofChangeCredential":"Choose a credential", + "ProofChangeCredential": "Choose a credential", "ProofRequest": "Proof Request", "ProofRequestDetails": "Proof Request Details", "ProofRequestAttributeDetails": "Proof Request Attribute Details", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index d548f6a701..7f729d938b 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -414,6 +414,7 @@ const translation = { "OfferDelay": "Retard de l'offre", "RejectThisProof?": "Rejeter cette preuve?", "AcceptingProof": "Acceptation de la preuve", + "MultipleCredentials": "You have multiple crdentials to choose from: (FR)", "SuccessfullyAcceptedProof": "Preuve acceptée avec succès", "SensitiveInformation": "This request is asking for sensitive information. (FR)", "RejectingProof": "Rejet de la preuve", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index 6b83fd596c..0f258ceab6 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -397,6 +397,7 @@ const translation = { "RejectThisProof?": "Rejeitar esta Requisição de Prova?", "DeclineThisProof?": "Recusar esta Requisição de Prova?", "AcceptingProof": "Aceitando Prova", + "MultipleCredentials": "You have multiple crdentials to choose from: (PB)", "SuccessfullyAcceptedProof": "Prova Aceita com Sucesso", "SensitiveInformation": "This request is asking for sensitive information. (PB)", "ProofRequestNotFound": "Requisição de Prova não encontrada.", diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index b27ab5a5c3..fe092c7c3a 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -24,13 +24,10 @@ const ProofChangeCredential: React.FC = ({ route, navigation } throw new Error('Change credential route params were not set properly') } const proofId = route.params.proofId - const fullCredentials = useCredentials().records const altCredentials = route.params.altCredentials const selectedCredentials = route.params.selectedCredentials - const proof = useProofById(proofId) const { ColorPallet, TextTheme } = useTheme() const { t } = useTranslation() - const { agent } = useAgent() const [loading, setLoading] = useState(false) const [proofItems, setProofItems] = useState([]) const [retrievedCredentials, setRetrievedCredentials] = useState() @@ -78,8 +75,26 @@ const ProofChangeCredential: React.FC = ({ route, navigation } if (value) { const { groupedProof, retrievedCredentials } = value setLoading(false) - setRetrievedCredentials(retrievedCredentials) - setProofItems(groupedProof.filter((proof) => altCredentials.includes(proof.credId))) + const activeCreds = groupedProof.filter((proof) => altCredentials.includes(proof.credId)) + const credList = activeCreds.map(cred => cred.credId) + const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? + { + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes).map(key => { return { [key]: retrievedCredentials.attributes[key].filter(attr => credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { + return { + ...prev, + ...curr + } + }, {}), + predicates: Object.keys(retrievedCredentials.predicates).map(key => { return { [key]: retrievedCredentials.predicates[key].filter(attr => credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { + return { + ...prev, + ...curr + } + }, {}) + } : undefined + setRetrievedCredentials(selectRetrievedCredentials) + setProofItems(activeCreds) } }) .catch((err: unknown) => { @@ -101,7 +116,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } ) : ( - You have multiple credentials to choose from: + {t('ProofRequest.MultipleCredentials')} )} ) @@ -117,6 +132,8 @@ const ProofChangeCredential: React.FC = ({ route, navigation } }, }) } + const hasSatisfiedPredicates = (fields: Fields, credId?: string) => + proofItems.flatMap(item => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) return ( @@ -136,11 +153,11 @@ const ProofChangeCredential: React.FC = ({ route, navigation } schemaId={item.schemaId} displayItems={[ ...(item.attributes ?? []), - ...evaluatePredicates(getCredentialsFields(), item.credDefId)(item), + ...evaluatePredicates(getCredentialsFields(), item.credId)(item), ]} credName={item.credName} existsInWallet={true} - satisfiedPredicates={true} + satisfiedPredicates={hasSatisfiedPredicates(getCredentialsFields(), item.credId)} proof={true} > diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 4188014a80..3ab8cf7247 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -67,7 +67,6 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const [pendingModalVisible, setPendingModalVisible] = useState(false) const [revocationOffense, setRevocationOffense] = useState(false) const [retrievedCredentials, setRetrievedCredentials] = useState() - const [proofItems, setProofItems] = useState([]) const [loading, setLoading] = useState(true) const [declineModalVisible, setDeclineModalVisible] = useState(false) const { ColorPallet, ListItems, TextTheme } = useTheme() @@ -181,7 +180,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const revDate = moment(item.revocationDate) return item.id.some((id) => { return Object.keys(fields).some((key) => { - + const dateIntervals = fields[key] ?.filter((attr) => attr.credentialId === id) .map((attr) => { @@ -207,7 +206,6 @@ const ProofRequest: React.FC = ({ navigation, route }) => { if (value) { const { groupedProof, retrievedCredentials, fullCredentials } = value setLoading(false) - setRetrievedCredentials(retrievedCredentials) let credList: string[] = [] if (selectedCredentials) { credList = selectedCredentials @@ -220,9 +218,27 @@ const ProofRequest: React.FC = ({ navigation, route }) => { } }) } + + const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? + { + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes).map(key => { return { [key]: retrievedCredentials.attributes[key].filter(attr=>credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { + return { + ...prev, + ...curr + } + }, {}), + predicates: Object.keys(retrievedCredentials.predicates).map(key => { return { [key]: retrievedCredentials.predicates[key].filter(attr=>credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { + return { + ...prev, + ...curr + } + }, {}) + } : undefined + setRetrievedCredentials(selectRetrievedCredentials) + const activeCreds = groupedProof.filter(item => credList.includes(item.credId)) setActiveCreds(activeCreds) - setProofItems(groupedProof) const unpackCredToField = (credentials: (ProofCredentialAttributes & ProofCredentialPredicates)[]): { [key: string]: Attribute[] & Predicate[] } => { return credentials.reduce((prev, current) => { @@ -273,17 +289,18 @@ const ProofRequest: React.FC = ({ navigation, route }) => { - const hasAvailableCredentials = (credDefId?: string): boolean => { + const hasAvailableCredentials = (credId?: string): boolean => { const fields = getCredentialsFields() - if (credDefId) { - return getCredentialInfo(credDefId, fields).some((credInfo) => credInfo.credentialDefinitionId === credDefId) + if (credId) { + return getCredentialInfo(credId, fields).some((credInfo) => credInfo.credentialId === credId) } return !!retrievedCredentials && Object.values(fields).every((c) => c.length > 0) } - const hasSatisfiedPredicates = (fields: Fields, credDefId?: string) => - activeCreds.flatMap(evaluatePredicates(fields, credDefId)).every((p) => p.satisfied) + const hasSatisfiedPredicates = (fields: Fields, credId?: string) => + activeCreds.flatMap(item => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) + const handleAcceptPress = async () => { try { @@ -475,22 +492,22 @@ const ProofRequest: React.FC = ({ navigation, route }) => { { return ( - {loading || !activeCreds.map(cred => cred.credId).includes(item.credId) ? null : ( + {loading ? null : ( 1} handleAltCredChange={(item.altCredentials && item.altCredentials.length > 1) ? () => { handleAltCredChange(item.altCredentials, item.credId) } : undefined} proof={true} diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index aaca9b27ed..e1d375391b 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -345,16 +345,16 @@ export type Fields = Record { +export const getCredentialInfo = (credId: string, fields: Fields): AnonCredsCredentialInfo[] => { const credentialInfo: AnonCredsCredentialInfo[] = [] Object.keys(fields).forEach((proofKey) => { credentialInfo.push(...fields[proofKey].map((attr) => attr.credentialInfo)) }) - return credDefId == undefined + return !credId ? credentialInfo - : credentialInfo.filter((cred) => cred.credentialDefinitionId === credDefId) + : credentialInfo.filter((cred) => cred.credentialId === credId) } /** @@ -389,19 +389,18 @@ const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPr * @returns Array of evaluated predicates */ export const evaluatePredicates = - (fields: Fields, credDefId?: string) => + (fields: Fields, credId?: string) => (proofCredentialItems: ProofCredentialItems): Predicate[] => { const predicates = proofCredentialItems.predicates - if (!predicates || predicates.length == 0) { return [] } - if (credDefId && credDefId != proofCredentialItems.credDefId || !proofCredentialItems.credDefId) { + if (credId && credId != proofCredentialItems.credId || !proofCredentialItems.credId) { return [] } - const credentialAttributes = getCredentialInfo(proofCredentialItems.credDefId, fields).map((ci) => ci.attributes) + const credentialAttributes = getCredentialInfo(proofCredentialItems.credId, fields).map((ci) => ci.attributes) return predicates.map((predicate) => { const { pType: pType, pValue: pValue, name: field } = predicate @@ -420,7 +419,7 @@ export const evaluatePredicates = } -export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: ProofExchangeRecord, fullCredentials: CredentialExchangeRecord[], t: TFunction<"translation", undefined>, allCreds?: boolean, credPreference?: string[]) => { +export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: ProofExchangeRecord, fullCredentials: CredentialExchangeRecord[], t: TFunction<"translation", undefined>) => { try { const format = await agent.proofs.getFormatData(proof.id) const hasAnonCreds = format.request?.anoncreds !== undefined @@ -469,8 +468,8 @@ export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: Pro const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy - const attributes = processProofAttributes(format.request, credentials, fullCredentials, allCreds, credPreference) - const predicates = processProofPredicates(format.request, credentials, fullCredentials, allCreds, credPreference) + const attributes = processProofAttributes(format.request, credentials, fullCredentials) + const predicates = processProofPredicates(format.request, credentials, fullCredentials) const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) return { groupedProof: groupedProof, retrievedCredentials: proofFormat, fullCredentials } @@ -489,8 +488,6 @@ export const processProofAttributes = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, credentialRecords?: CredentialExchangeRecord[], - allCreds?: boolean, - credPreference?: string[] ): { [key: string]: ProofCredentialAttributes } => { const processedAttributes = {} as { [key: string]: ProofCredentialAttributes } @@ -506,15 +503,7 @@ export const processProofAttributes = ( const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) let credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) - if (!allCreds) { - let cred = undefined - if (credPreference) { - cred = credentialList.find(cred => credPreference.includes(cred.credId)) ?? credentialList.shift() - } else { - cred = credentialList.shift() - } - credentialList = cred ? [cred] : [] as AnonCredsRequestedAttributeMatch[] - } + //iterate over all credentials that satisfy the proof for (const credential of credentialList) { @@ -577,8 +566,6 @@ export const processProofPredicates = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, credentialRecords?: CredentialExchangeRecord[], - allCreds?: boolean, - credPreference?: string[] ): { [key: string]: ProofCredentialPredicates } => { const processedPredicates = {} as { [key: string]: ProofCredentialPredicates } @@ -594,15 +581,7 @@ export const processProofPredicates = ( const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) let credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) - if (!allCreds) { - let cred = undefined - if (credPreference) { - cred = credentialList.find(cred => credPreference.includes(cred.credId)) ?? credentialList.shift() - } else { - cred = credentialList.shift() - } - credentialList = cred ? [cred] : [] as AnonCredsRequestedAttributeMatch[] - } + for (const credential of credentialList) { let revoked = false @@ -615,7 +594,7 @@ export const processProofPredicates = ( } else { continue } - const { credentialId, credentialDefinitionId, schemaId } = { ...credential, ...credential?.credentialInfo } + const { credentialDefinitionId, schemaId } = { ...credential, ...credential?.credentialInfo } const { name, p_type: pType, p_value: pValue, non_revoked } = requestedProofPredicates[key] const credNameRestriction = credNameFromRestriction(requestedProofPredicates[key]?.restrictions) diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap index c0b1c62765..d7d14ad1ce 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap @@ -309,6 +309,7 @@ exports[`displays a credential offer screen accepting a credential 1`] = ` "elevation": 0, "overflow": "hidden", }, + undefined, ] } > @@ -505,6 +506,7 @@ exports[`displays a credential offer screen accepting a credential 1`] = ` @@ -2323,6 +2326,7 @@ exports[`displays a credential offer screen declining a credential 1`] = ` @@ -4143,6 +4148,7 @@ exports[`displays a credential offer screen renders correctly 1`] = ` Date: Mon, 25 Sep 2023 13:30:01 -0700 Subject: [PATCH 03/15] fixed nav issue from chat screen Signed-off-by: wadeking98 --- .../App/components/misc/CredentialCard11.tsx | 40 +-- packages/legacy/core/App/hooks/proofs.ts | 17 +- .../core/App/navigators/ProofRequestStack.tsx | 8 +- .../App/screens/ProofChangeCredential.tsx | 103 ++++---- .../legacy/core/App/screens/ProofRequest.tsx | 234 +++++++++++------- packages/legacy/core/App/types/navigators.ts | 4 +- packages/legacy/core/App/utils/helpers.ts | 153 ++++++------ packages/oca/src/legacy/resolver/record.ts | 2 +- 8 files changed, 314 insertions(+), 247 deletions(-) diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index 6aef61969a..e70f34c902 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -1,6 +1,8 @@ import { CredentialExchangeRecord } from '@aries-framework/core' import { BrandingOverlay } from '@hyperledger/aries-oca' import { Attribute, CredentialOverlay, Predicate } from '@hyperledger/aries-oca/build/legacy' +import { useNavigation } from '@react-navigation/core' +import { StackNavigationProp } from '@react-navigation/stack' import startCase from 'lodash.startcase' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -11,14 +13,12 @@ import Icon from 'react-native-vector-icons/MaterialIcons' import { useConfiguration } from '../../contexts/configuration' import { useTheme } from '../../contexts/theme' import { GenericFn } from '../../types/fn' +import { ProofRequestsStackParams, Screens, Stacks } from '../../types/navigators' import { credentialTextColor, getCredentialIdentifiers, toImageSource } from '../../utils/credential' import { getCredentialConnectionLabel, isDataUrl } from '../../utils/helpers' import { testIdWithKey } from '../../utils/testable' import CardWatermark from './CardWatermark' -import { useNavigation } from '@react-navigation/core' -import { ProofRequestsStackParams, Screens, Stacks } from '../../types/navigators' -import { StackNavigationProp } from '@react-navigation/stack' interface CredentialCard11Props { credential?: CredentialExchangeRecord @@ -79,7 +79,7 @@ const CredentialCard11: React.FC = ({ schemaId, proof, hasAltCredentials, - handleAltCredChange + handleAltCredChange, }) => { const { width } = useWindowDimensions() const borderRadius = 10 @@ -201,19 +201,19 @@ const CredentialCard11: React.FC = ({ selectedCred: { borderWidth: 5, borderRadius: 15, - borderColor: ColorPallet.semantic.focus + borderColor: ColorPallet.semantic.focus, }, seperator: { - width: "100%", + width: '100%', height: 2, marginVertical: 10, - backgroundColor: ColorPallet.grayscale.lightGrey + backgroundColor: ColorPallet.grayscale.lightGrey, }, credActionText: { fontSize: 20, fontWeight: 'bold', - color: ColorPallet.brand.link - } + color: ColorPallet.brand.link, + }, }) const parseAttribute = (item: (Attribute & Predicate) | undefined) => { @@ -447,12 +447,18 @@ const CredentialCard11: React.FC = ({ }} ListFooterComponent={ hasAltCredentials ? ( - + - + Change credential - + @@ -548,7 +554,8 @@ const CredentialCard11: React.FC = ({ style={styles.cardContainer} accessible={true} accessibilityLabel={ - `${overlay.metaOverlay?.issuer ? `${t('Credentials.IssuedBy')} ${overlay.metaOverlay?.issuer}` : ''}, ${overlay.metaOverlay?.watermark ?? '' + `${overlay.metaOverlay?.issuer ? `${t('Credentials.IssuedBy')} ${overlay.metaOverlay?.issuer}` : ''}, ${ + overlay.metaOverlay?.watermark ?? '' } ${overlay.metaOverlay?.name ?? ''} ${t('Credentials.Credential')}.` + cardData.map((item) => { const { label, value } = parseAttribute(item as (Attribute & Predicate) | undefined) @@ -567,7 +574,12 @@ const CredentialCard11: React.FC = ({ } return overlay.bundle ? ( { setDimensions({ cardHeight: event.nativeEvent.layout.height, cardWidth: event.nativeEvent.layout.width }) }} diff --git a/packages/legacy/core/App/hooks/proofs.ts b/packages/legacy/core/App/hooks/proofs.ts index 9bcaefc31d..1d8acb3ea8 100644 --- a/packages/legacy/core/App/hooks/proofs.ts +++ b/packages/legacy/core/App/hooks/proofs.ts @@ -1,9 +1,10 @@ import { ProofExchangeRecord } from '@aries-framework/core' import { useAgent, useCredentials, useProofById, useProofs } from '@aries-framework/react-hooks' import { useMemo } from 'react' -import { retrieveCredentialsForProof } from '../utils/helpers' import { useTranslation } from 'react-i18next' +import { retrieveCredentialsForProof } from '../utils/helpers' + export const useProofsByConnectionId = (connectionId: string): ProofExchangeRecord[] => { const { records: proofs } = useProofs() return useMemo( @@ -17,12 +18,10 @@ export const getAllCredentialsForProof = (proofId: string) => { const { agent } = useAgent() const fullCredentials = useCredentials().records const proof = useProofById(proofId) - return useMemo( - () => { - if (!proof || !agent) { - return - } - return retrieveCredentialsForProof(agent, proof, fullCredentials, t) - }, [proofId] - ) + return useMemo(() => { + if (!proof || !agent) { + return + } + return retrieveCredentialsForProof(agent, proof, fullCredentials, t) + }, [proofId]) } diff --git a/packages/legacy/core/App/navigators/ProofRequestStack.tsx b/packages/legacy/core/App/navigators/ProofRequestStack.tsx index 42efc7933a..5f1564980a 100644 --- a/packages/legacy/core/App/navigators/ProofRequestStack.tsx +++ b/packages/legacy/core/App/navigators/ProofRequestStack.tsx @@ -6,12 +6,12 @@ import HeaderButton, { ButtonLocation } from '../components/buttons/HeaderButton import HeaderRightHome from '../components/buttons/HeaderHome' import { useTheme } from '../contexts/theme' import ListProofRequests from '../screens/ListProofRequests' +import ProofChangeCredential from '../screens/ProofChangeCredential' import ProofDetails from '../screens/ProofDetails' import ProofRequestDetails from '../screens/ProofRequestDetails' import ProofRequestUsageHistory from '../screens/ProofRequestUsageHistory' import ProofRequesting from '../screens/ProofRequesting' import { ProofRequestsStackParams, Screens } from '../types/navigators' -import ProofChangeCredential from '../screens/ProofChangeCredential' import { testIdWithKey } from '../utils/testable' import { createDefaultStackOptions } from './defaultStackOptions' @@ -36,7 +36,11 @@ const ProofRequestStack: React.FC = () => { title: '', })} /> - + @@ -24,14 +23,14 @@ const ProofChangeCredential: React.FC = ({ route, navigation } throw new Error('Change credential route params were not set properly') } const proofId = route.params.proofId + const selectedCred = route.params.selectedCred const altCredentials = route.params.altCredentials - const selectedCredentials = route.params.selectedCredentials + const onCredChange = route.params.onCredChange const { ColorPallet, TextTheme } = useTheme() const { t } = useTranslation() const [loading, setLoading] = useState(false) const [proofItems, setProofItems] = useState([]) const [retrievedCredentials, setRetrievedCredentials] = useState() - const [selectedCred, setSelectedCred] = useState('') const credProofPromise = getAllCredentialsForProof(proofId) const styles = StyleSheet.create({ pageContainer: { @@ -55,48 +54,58 @@ const ProofChangeCredential: React.FC = ({ route, navigation } }, }) - useEffect(() => { - if (proofItems) { - const displayedCredIds: string[] = proofItems.map((item) => item.credId) - setSelectedCred(displayedCredIds.find((id) => selectedCredentials.includes(id)) ?? '') - } - }, [proofItems]) - const getCredentialsFields = (): Fields => ({ ...retrievedCredentials?.attributes, ...retrievedCredentials?.predicates, }) useEffect(() => { - setLoading(true) - credProofPromise?.then((value) => { - if (value) { - const { groupedProof, retrievedCredentials } = value - setLoading(false) - const activeCreds = groupedProof.filter((proof) => altCredentials.includes(proof.credId)) - const credList = activeCreds.map(cred => cred.credId) - const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? - { - ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes).map(key => { return { [key]: retrievedCredentials.attributes[key].filter(attr => credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { - return { - ...prev, - ...curr - } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates).map(key => { return { [key]: retrievedCredentials.predicates[key].filter(attr => credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { - return { - ...prev, - ...curr + credProofPromise + ?.then((value) => { + if (value) { + const { groupedProof, retrievedCredentials } = value + setLoading(false) + const activeCreds = groupedProof.filter((proof) => altCredentials.includes(proof.credId)) + const credList = activeCreds.map((cred) => cred.credId) + const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials + ? { + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes) + .map((key) => { + return { + [key]: retrievedCredentials.attributes[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), + predicates: Object.keys(retrievedCredentials.predicates) + .map((key) => { + return { + [key]: retrievedCredentials.predicates[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), } - }, {}) - } : undefined - setRetrievedCredentials(selectRetrievedCredentials) - setProofItems(activeCreds) - } - }) + : undefined + setRetrievedCredentials(selectRetrievedCredentials) + setProofItems(activeCreds) + } + }) .catch((err: unknown) => { const error = new BifoldError( t('Error.Title1026'), @@ -122,18 +131,12 @@ const ProofChangeCredential: React.FC = ({ route, navigation } ) } - const changeCred = (credId: string, altCreds: string[]) => { - const newSelectedCreds = selectedCredentials.filter(id => !altCreds.includes(id)) - navigation.getParent()?.navigate(Stacks.NotificationStack, { - screen: Screens.ProofRequest, - params: { - proofId, - selectedCredentials: [credId, ...newSelectedCreds], - }, - }) + const changeCred = (credId: string) => { + onCredChange(credId) + navigation.getParent()?.goBack() } const hasSatisfiedPredicates = (fields: Fields, credId?: string) => - proofItems.flatMap(item => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) + proofItems.flatMap((item) => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) return ( diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 3ab8cf7247..c24a069607 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -1,14 +1,10 @@ import type { StackScreenProps } from '@react-navigation/stack' import { - AnonCredsCredentialInfo, AnonCredsCredentialsForProofRequest, - AnonCredsPredicateType, - AnonCredsRequestedAttributeMatch, - AnonCredsRequestedPredicateMatch, } from '@aries-framework/anoncreds' -import { CredentialExchangeRecord, ProofExchangeRecord } from '@aries-framework/core' -import { useConnectionById, useCredentials, useProofById } from '@aries-framework/react-hooks' +import { CredentialExchangeRecord } from '@aries-framework/core' +import { useConnectionById, useProofById } from '@aries-framework/react-hooks' import { Attribute, Predicate, @@ -18,9 +14,9 @@ import { } from '@hyperledger/aries-oca/build/legacy' import { useIsFocused } from '@react-navigation/core' import moment from 'moment' -import React, { useEffect, useMemo, useState } from 'react' +import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { DeviceEventEmitter, FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native' +import { DeviceEventEmitter, FlatList, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import Icon from 'react-native-vector-icons/MaterialIcons' @@ -38,17 +34,20 @@ import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' import { useTour } from '../contexts/tour/tour-context' import { useOutOfBandByConnectionId } from '../hooks/connections' +import { getAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' import { NotificationStackParams, Screens, Stacks, TabStacks } from '../types/navigators' import { ModalUsage } from '../types/remove' import { TourID } from '../types/tour' import { useAppAgent } from '../utils/agent' -import { getCredentialIdentifiers } from '../utils/credential' -import { Fields, evaluatePredicates, getCredentialInfo, mergeAttributesAndPredicates, processProofAttributes, processProofPredicates, retrieveCredentialsForProof } from '../utils/helpers' +import { + Fields, + evaluatePredicates, + getCredentialInfo, +} from '../utils/helpers' import { testIdWithKey } from '../utils/testable' import ProofRequestAccept from './ProofRequestAccept' -import { getAllCredentialsForProof } from '../hooks/proofs' type ProofRequestProps = StackScreenProps @@ -57,7 +56,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { throw new Error('ProofRequest route prams were not set properly') } - const { proofId, selectedCredentials } = route?.params + const { proofId } = route?.params const { agent } = useAppAgent() const { t } = useTranslation() const { assertConnectedNetwork } = useNetwork() @@ -75,12 +74,12 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const { enableTours: enableToursConfig, OCABundleResolver } = useConfiguration() const [containsPI, setContainsPI] = useState(false) const [activeCreds, setActiveCreds] = useState([]) + const [selectedCredentials, setSelectedCredentials] = useState([]) const [store, dispatch] = useStore() const credProofPromise = getAllCredentialsForProof(proofId) const { start } = useTour() const screenIsFocused = useIsFocused() - const styles = StyleSheet.create({ pageContainer: { flex: 1, @@ -180,7 +179,6 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const revDate = moment(item.revocationDate) return item.id.some((id) => { return Object.keys(fields).some((key) => { - const dateIntervals = fields[key] ?.filter((attr) => attr.credentialId === id) .map((attr) => { @@ -198,69 +196,90 @@ const ProofRequest: React.FC = ({ navigation, route }) => { }) } - useEffect(() => { - setLoading(true) - credProofPromise?.then((value) => { - if (value) { - const { groupedProof, retrievedCredentials, fullCredentials } = value - setLoading(false) - let credList: string[] = [] - if (selectedCredentials) { - credList = selectedCredentials - } else { - // we only want one of each satisfying credential - groupedProof.forEach(item => { - const credId = item.altCredentials[0] - if (!credList.includes(credId)) { - credList.push(credId) - } - }) - } - - const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? - { - ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes).map(key => { return { [key]: retrievedCredentials.attributes[key].filter(attr=>credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { - return { - ...prev, - ...curr - } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates).map(key => { return { [key]: retrievedCredentials.predicates[key].filter(attr=>credList.includes(attr.credentialId)) } }).reduce((prev, curr) => { - return { - ...prev, - ...curr + credProofPromise + ?.then((value) => { + if (value) { + const { groupedProof, retrievedCredentials, fullCredentials } = value + setLoading(false) + let credList: string[] = [] + if (selectedCredentials.length > 0) { + credList = selectedCredentials + } else { + // we only want one of each satisfying credential + groupedProof.forEach((item) => { + const credId = item.altCredentials[0] + if (!credList.includes(credId)) { + credList.push(credId) } - }, {}) - } : undefined - setRetrievedCredentials(selectRetrievedCredentials) + }) + } - const activeCreds = groupedProof.filter(item => credList.includes(item.credId)) - setActiveCreds(activeCreds) + const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials + ? { + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes) + .map((key) => { + return { + [key]: retrievedCredentials.attributes[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), + predicates: Object.keys(retrievedCredentials.predicates) + .map((key) => { + return { + [key]: retrievedCredentials.predicates[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), + } + : undefined + setRetrievedCredentials(selectRetrievedCredentials) - const unpackCredToField = (credentials: (ProofCredentialAttributes & ProofCredentialPredicates)[]): { [key: string]: Attribute[] & Predicate[] } => { - return credentials.reduce((prev, current) => { - return { ...prev, [current.credId]: current.attributes ?? current.predicates ?? [] } - }, {}) - } + const activeCreds = groupedProof.filter((item) => credList.includes(item.credId)) + setActiveCreds(activeCreds) - const records = fullCredentials.filter(record => record.credentials.some(cred => credList.includes(cred.credentialRecordId))) - const foundRevocationOffense = - containsRevokedCreds(records, unpackCredToField(activeCreds)) || containsRevokedCreds(records, unpackCredToField(activeCreds)) - setRevocationOffense(foundRevocationOffense) - } - }).catch((err: unknown) => { - const error = new BifoldError( - t('Error.Title1026'), - t('Error.Message1026'), - (err as Error)?.message ?? err, - 1026 - ) - DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) - }) + const unpackCredToField = ( + credentials: (ProofCredentialAttributes & ProofCredentialPredicates)[] + ): { [key: string]: Attribute[] & Predicate[] } => { + return credentials.reduce((prev, current) => { + return { ...prev, [current.credId]: current.attributes ?? current.predicates ?? [] } + }, {}) + } + const records = fullCredentials.filter((record) => + record.credentials.some((cred) => credList.includes(cred.credentialRecordId)) + ) + const foundRevocationOffense = + containsRevokedCreds(records, unpackCredToField(activeCreds)) || + containsRevokedCreds(records, unpackCredToField(activeCreds)) + setRevocationOffense(foundRevocationOffense) + } + }) + .catch((err: unknown) => { + const error = new BifoldError( + t('Error.Title1026'), + t('Error.Message1026'), + (err as Error)?.message ?? err, + 1026 + ) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) + }) }, [selectedCredentials]) const toggleDeclineModalVisible = () => setDeclineModalVisible(!declineModalVisible) @@ -277,7 +296,9 @@ const ProofRequest: React.FC = ({ navigation, route }) => { return false } const labels = (item.attributes ?? []).map((field) => field.label ?? field.name ?? '') - const bundle = await OCABundleResolver.resolveAllBundles({ identifiers: { credentialDefinitionId: item.credDefId, schemaId: item.schemaId } }) + const bundle = await OCABundleResolver.resolveAllBundles({ + identifiers: { credentialDefinitionId: item.credDefId, schemaId: item.schemaId }, + }) const flaggedAttributes: string[] = (bundle as any).bundle.bundle.flaggedAttributes.map((attr: any) => attr.name) const foundPI = labels.some((label) => flaggedAttributes.includes(label)) setContainsPI(foundPI) @@ -285,10 +306,6 @@ const ProofRequest: React.FC = ({ navigation, route }) => { }) }, [activeCreds]) - - - - const hasAvailableCredentials = (credId?: string): boolean => { const fields = getCredentialsFields() @@ -298,9 +315,8 @@ const ProofRequest: React.FC = ({ navigation, route }) => { return !!retrievedCredentials && Object.values(fields).every((c) => c.length > 0) } - const hasSatisfiedPredicates = (fields: Fields, credId?: string) => - activeCreds.flatMap(item => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) - + const hasSatisfiedPredicates = (fields: Fields, credId?: string) => + activeCreds.flatMap((item) => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) const handleAcceptPress = async () => { try { @@ -320,24 +336,31 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const credObject = { ...retrievedCredentials, attributes: Object.keys(retrievedCredentials.attributes) - .map(key => { + .map((key) => { return { - [key]: retrievedCredentials.attributes[key] - .find(cred => activeCreds.map(item => item.credId).includes(cred.credentialId)) + [key]: retrievedCredentials.attributes[key].find((cred) => + activeCreds.map((item) => item.credId).includes(cred.credentialId) + ), } - }).reduce((prev, current) => { return { ...prev, ...current } }), + }) + .reduce((prev, current) => { + return { ...prev, ...current } + }), predicates: Object.keys(retrievedCredentials.predicates) - .map(key => { + .map((key) => { return { - [key]: retrievedCredentials.predicates[key] - .find(cred => activeCreds.map(item => item.credId).includes(cred.credentialId)) + [key]: retrievedCredentials.predicates[key].find((cred) => + activeCreds.map((item) => item.credId).includes(cred.credentialId) + ), } - }).reduce((prev, current) => { return { ...prev, ...current } }, {}), - selfAttestedAttributes: {} + }) + .reduce((prev, current) => { + return { ...prev, ...current } + }, {}), + selfAttestedAttributes: {}, } const automaticRequestedCreds = { proofFormats: { [formatToUse]: { ...credObject } } } - if (!automaticRequestedCreds) { throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) } @@ -455,8 +478,20 @@ const ProofRequest: React.FC = ({ navigation, route }) => { ) } - const handleAltCredChange = (altCredentials: string[]) => { - navigation.getParent()?.navigate(Stacks.ProofRequestsStack, { screen: Screens.ProofChangeCredential, params: { altCredentials, proofId, selectedCredentials: selectedCredentials ?? activeCreds.map(item => item.credId) } }) + const handleAltCredChange = (selectedCred: string, altCredentials: string[]) => { + const onCredChange = (cred: string) => { + const newSelectedCreds = (selectedCredentials.length > 0 ? selectedCredentials : activeCreds.map(item => item.credId)).filter((id) => !altCredentials.includes(id)) + setSelectedCredentials([cred, ...newSelectedCreds]) + } + navigation.getParent()?.navigate(Stacks.ProofRequestsStack, { + screen: Screens.ProofChangeCredential, + params: { + selectedCred, + altCredentials, + proofId, + onCredChange + }, + }) } const proofPageFooter = () => { @@ -472,7 +507,9 @@ const ProofRequest: React.FC = ({ navigation, route }) => { testID={testIdWithKey('Share')} buttonType={ButtonType.Primary} onPress={handleAcceptPress} - disabled={!hasAvailableCredentials() || !hasSatisfiedPredicates(getCredentialsFields()) || revocationOffense} + disabled={ + !hasAvailableCredentials() || !hasSatisfiedPredicates(getCredentialsFields()) || revocationOffense + } /> @@ -499,17 +536,26 @@ const ProofRequest: React.FC = ({ navigation, route }) => { return ( {loading ? null : ( - + 1} - handleAltCredChange={(item.altCredentials && item.altCredentials.length > 1) ? () => { handleAltCredChange(item.altCredentials, item.credId) } : undefined} + handleAltCredChange={ + item.altCredentials && item.altCredentials.length > 1 + ? () => { + handleAltCredChange(item.credId, item.altCredentials) + } + : undefined + } proof={true} > diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index 7a6bc014bc..a1347062ad 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -104,7 +104,7 @@ export type ProofRequestsStackParams = { [Screens.ProofDetails]: { recordId: string; isHistory?: boolean; senderReview?: boolean } [Screens.ProofRequestDetails]: { templateId: string; connectionId?: string } [Screens.ProofRequestUsageHistory]: { templateId: string } - [Screens.ProofChangeCredential]: { altCredentials: string[], proofId: string, selectedCredentials: string[] } + [Screens.ProofChangeCredential]: { selectedCred:string, altCredentials: string[]; proofId: string; onCredChange: (arg: string) => void } } export type CredentialStackParams = { @@ -137,7 +137,7 @@ export type SettingStackParams = { export type NotificationStackParams = { [Screens.CredentialDetails]: { credentialId: string } [Screens.CredentialOffer]: { credentialId: string } - [Screens.ProofRequest]: { proofId: string, selectedCredentials?: string[] } + [Screens.ProofRequest]: { proofId: string; selectedCredentials?: string[] } [Screens.CustomNotification]: undefined [Screens.ProofDetails]: { recordId: string } } diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index e1d375391b..8d1ee5cb1e 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -43,9 +43,11 @@ import { Role } from '../types/chat' import { ChildFn } from '../types/tour' export { parsedCredDefNameFromCredential } from './cred-def' -import { parseCredDefFromId } from './cred-def' import { BifoldAgent } from './agent' +import { parseCredDefFromId } from './cred-def' + import { BifoldError } from '../types/error' + import { DeviceEventEmitter } from 'react-native' import { TFunction } from 'react-i18next' @@ -183,8 +185,8 @@ export function formatTime( ? momentTime.format(formatString) : // if non-english, don't include comma between year and month isNonEnglish - ? `${momentTime.format(formatString)} ${momentTime.format('YYYY')}` - : `${momentTime.format(formatString)}, ${momentTime.format('YYYY')}` + ? `${momentTime.format(formatString)} ${momentTime.format('YYYY')}` + : `${momentTime.format(formatString)}, ${momentTime.format('YYYY')}` if (includeHour) { formattedTime = `${formattedTime}, ${momentTime.format('h:mm a')}` } @@ -341,10 +343,10 @@ export const isDataUrl = (value: string | number | null) => { export type Fields = Record /** - * Retrieve current credentials info filtered by `credentialDefinitionId` if given. - * @param credDefId Credential Definition Id - * @returns Array of `AnonCredsCredentialInfo` - */ + * Retrieve current credentials info filtered by `credentialDefinitionId` if given. + * @param credDefId Credential Definition Id + * @returns Array of `AnonCredsCredentialInfo` + */ export const getCredentialInfo = (credId: string, fields: Fields): AnonCredsCredentialInfo[] => { const credentialInfo: AnonCredsCredentialInfo[] = [] @@ -352,18 +354,16 @@ export const getCredentialInfo = (credId: string, fields: Fields): AnonCredsCred credentialInfo.push(...fields[proofKey].map((attr) => attr.credentialInfo)) }) - return !credId - ? credentialInfo - : credentialInfo.filter((cred) => cred.credentialId === credId) + return !credId ? credentialInfo : credentialInfo.filter((cred) => cred.credentialId === credId) } /** - * Evaluate if given attribute value satisfies the predicate. - * @param attribute Credential attribute value - * @param pValue Predicate value - * @param pType Predicate type ({@link AnonCredsPredicateType}) - * @returns `true`if predicate is satisfied, otherwise `false` - */ + * Evaluate if given attribute value satisfies the predicate. + * @param attribute Credential attribute value + * @param pValue Predicate value + * @param pType Predicate type ({@link AnonCredsPredicateType}) + * @returns `true`if predicate is satisfied, otherwise `false` + */ const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPredicateType): boolean => { if (pType === '>=') { return attribute >= pValue @@ -390,36 +390,40 @@ const evaluateOperation = (attribute: number, pValue: number, pType: AnonCredsPr */ export const evaluatePredicates = (fields: Fields, credId?: string) => - (proofCredentialItems: ProofCredentialItems): Predicate[] => { - const predicates = proofCredentialItems.predicates - if (!predicates || predicates.length == 0) { - return [] - } + (proofCredentialItems: ProofCredentialItems): Predicate[] => { + const predicates = proofCredentialItems.predicates + if (!predicates || predicates.length == 0) { + return [] + } - if (credId && credId != proofCredentialItems.credId || !proofCredentialItems.credId) { - return [] - } + if ((credId && credId != proofCredentialItems.credId) || !proofCredentialItems.credId) { + return [] + } - const credentialAttributes = getCredentialInfo(proofCredentialItems.credId, fields).map((ci) => ci.attributes) + const credentialAttributes = getCredentialInfo(proofCredentialItems.credId, fields).map((ci) => ci.attributes) - return predicates.map((predicate) => { - const { pType: pType, pValue: pValue, name: field } = predicate - let satisfied = false + return predicates.map((predicate) => { + const { pType: pType, pValue: pValue, name: field } = predicate + let satisfied = false - if (field) { - const attribute = (credentialAttributes.find((attr) => attr[field] != undefined) ?? {})[field] + if (field) { + const attribute = (credentialAttributes.find((attr) => attr[field] != undefined) ?? {})[field] - if (attribute && pValue) { - satisfied = evaluateOperation(Number(attribute), Number(pValue), pType as AnonCredsPredicateType) - } + if (attribute && pValue) { + satisfied = evaluateOperation(Number(attribute), Number(pValue), pType as AnonCredsPredicateType) } + } - return { ...predicate, satisfied } - }) - } - + return { ...predicate, satisfied } + }) + } -export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: ProofExchangeRecord, fullCredentials: CredentialExchangeRecord[], t: TFunction<"translation", undefined>) => { +export const retrieveCredentialsForProof = async ( + agent: BifoldAgent, + proof: ProofExchangeRecord, + fullCredentials: CredentialExchangeRecord[], + t: TFunction<'translation', undefined> +) => { try { const format = await agent.proofs.getFormatData(proof.id) const hasAnonCreds = format.request?.anoncreds !== undefined @@ -431,26 +435,26 @@ export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: Pro // We should ignore the key, if the value is undefined. For now this is a workaround. ...(hasIndy ? { - indy: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } + indy: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } : {}), ...(hasAnonCreds ? { - anoncreds: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } + anoncreds: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } : {}), }, }) @@ -474,12 +478,7 @@ export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: Pro const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) return { groupedProof: groupedProof, retrievedCredentials: proofFormat, fullCredentials } } catch (err: unknown) { - const error = new BifoldError( - t('Error.Title1043'), - t('Error.Message1043'), - (err as Error)?.message ?? err, - 1043 - ) + const error = new BifoldError(t('Error.Title1043'), t('Error.Message1043'), (err as Error)?.message ?? err, 1043) DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) } } @@ -487,7 +486,7 @@ export const retrieveCredentialsForProof = async (agent: BifoldAgent, proof: Pro export const processProofAttributes = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, - credentialRecords?: CredentialExchangeRecord[], + credentialRecords?: CredentialExchangeRecord[] ): { [key: string]: ProofCredentialAttributes } => { const processedAttributes = {} as { [key: string]: ProofCredentialAttributes } @@ -500,13 +499,14 @@ export const processProofAttributes = ( } for (const key of Object.keys(retrievedCredentialAttributes)) { - const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) + const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])] + .sort(credentialSortFn) + .map((cred) => cred.credentialId) - let credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) + const credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) //iterate over all credentials that satisfy the proof for (const credential of credentialList) { - const credNameRestriction = credNameFromRestriction(requestedProofAttributes[key]?.restrictions) let credName = credNameRestriction ?? key @@ -519,8 +519,8 @@ export const processProofAttributes = ( let revoked = false let credExchangeRecord = undefined if (credential) { - credExchangeRecord = credentialRecords?.find( - (record) => record.credentials.map(cred => cred.credentialRecordId).includes(credential.credentialId) + credExchangeRecord = credentialRecords?.find((record) => + record.credentials.map((cred) => cred.credentialRecordId).includes(credential.credentialId) ) revoked = credExchangeRecord?.revocationNotification !== undefined } else { @@ -553,7 +553,7 @@ export const processProofAttributes = ( credentialId: credential.credentialId, name: attributeName, value: attributeValue, - nonRevoked: non_revoked + nonRevoked: non_revoked, }) ) } @@ -565,7 +565,7 @@ export const processProofAttributes = ( export const processProofPredicates = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, - credentialRecords?: CredentialExchangeRecord[], + credentialRecords?: CredentialExchangeRecord[] ): { [key: string]: ProofCredentialPredicates } => { const processedPredicates = {} as { [key: string]: ProofCredentialPredicates } @@ -578,17 +578,18 @@ export const processProofPredicates = ( } for (const key of Object.keys(requestedProofPredicates)) { - const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn).map(cred => cred.credentialId) + const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])] + .sort(credentialSortFn) + .map((cred) => cred.credentialId) - let credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) + const credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) for (const credential of credentialList) { - let revoked = false let credExchangeRecord = undefined if (credential) { - credExchangeRecord = credentialRecords?.find( - (record) => record.credentials.map(cred => cred.credentialRecordId).includes(credential.credentialId) + credExchangeRecord = credentialRecords?.find((record) => + record.credentials.map((cred) => cred.credentialRecordId).includes(credential.credentialId) ) revoked = credExchangeRecord?.revocationNotification !== undefined } else { @@ -626,7 +627,7 @@ export const processProofPredicates = ( revoked, pValue, pType, - nonRevoked: non_revoked + nonRevoked: non_revoked, }) ) } @@ -642,7 +643,9 @@ export const mergeAttributesAndPredicates = ( for (const [key, predicate] of Object.entries(predicates)) { const existingEntry = merged[key] if (existingEntry) { - const mergedAltCreds = existingEntry.altCredentials?.filter(credId => predicate.altCredentials?.includes(credId)) + const mergedAltCreds = existingEntry.altCredentials?.filter((credId) => + predicate.altCredentials?.includes(credId) + ) merged[key] = { ...existingEntry, ...predicate } merged[key].altCredentials = mergedAltCreds } else { diff --git a/packages/oca/src/legacy/resolver/record.ts b/packages/oca/src/legacy/resolver/record.ts index 46e08952b6..9c8f9eb5ff 100644 --- a/packages/oca/src/legacy/resolver/record.ts +++ b/packages/oca/src/legacy/resolver/record.ts @@ -91,7 +91,7 @@ export interface ProofCredentialAttributes { export interface ProofCredentialPredicates { altCredentials?: string[] credExchangeRecord?: CredentialExchangeRecord - credId:string + credId: string credDefId?: string schemaId?: string credName: string From 409ed503fe4885b4316f078acde4246b72f9bb65 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Mon, 25 Sep 2023 14:09:40 -0700 Subject: [PATCH 04/15] fixed linting Signed-off-by: wadeking98 --- .../App/components/misc/CredentialCard11.tsx | 3 - .../App/screens/ProofChangeCredential.tsx | 3 +- .../legacy/core/App/screens/ProofRequest.tsx | 80 ++++----- packages/legacy/core/App/types/navigators.ts | 7 +- packages/legacy/core/App/utils/helpers.ts | 168 +++++++++--------- 5 files changed, 128 insertions(+), 133 deletions(-) diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index e70f34c902..fc3d47b0a7 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -1,8 +1,6 @@ import { CredentialExchangeRecord } from '@aries-framework/core' import { BrandingOverlay } from '@hyperledger/aries-oca' import { Attribute, CredentialOverlay, Predicate } from '@hyperledger/aries-oca/build/legacy' -import { useNavigation } from '@react-navigation/core' -import { StackNavigationProp } from '@react-navigation/stack' import startCase from 'lodash.startcase' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,7 +11,6 @@ import Icon from 'react-native-vector-icons/MaterialIcons' import { useConfiguration } from '../../contexts/configuration' import { useTheme } from '../../contexts/theme' import { GenericFn } from '../../types/fn' -import { ProofRequestsStackParams, Screens, Stacks } from '../../types/navigators' import { credentialTextColor, getCredentialIdentifiers, toImageSource } from '../../utils/credential' import { getCredentialConnectionLabel, isDataUrl } from '../../utils/helpers' import { testIdWithKey } from '../../utils/testable' diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index ac98dd3688..5703350e61 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -1,6 +1,5 @@ import { AnonCredsCredentialsForProofRequest } from '@aries-framework/anoncreds' import { ProofCredentialItems } from '@hyperledger/aries-oca/build/legacy' -import { useNavigation } from '@react-navigation/core' import { StackScreenProps } from '@react-navigation/stack' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,7 +12,7 @@ import { EventTypes } from '../constants' import { useTheme } from '../contexts/theme' import { getAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' -import { ProofRequestsStackParams, Screens, Stacks } from '../types/navigators' +import { ProofRequestsStackParams, Screens } from '../types/navigators' import { Fields, evaluatePredicates } from '../utils/helpers' type ProofChangeProps = StackScreenProps diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index c24a069607..c243fcaecd 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -1,8 +1,6 @@ import type { StackScreenProps } from '@react-navigation/stack' -import { - AnonCredsCredentialsForProofRequest, -} from '@aries-framework/anoncreds' +import { AnonCredsCredentialsForProofRequest } from '@aries-framework/anoncreds' import { CredentialExchangeRecord } from '@aries-framework/core' import { useConnectionById, useProofById } from '@aries-framework/react-hooks' import { @@ -40,11 +38,7 @@ import { NotificationStackParams, Screens, Stacks, TabStacks } from '../types/na import { ModalUsage } from '../types/remove' import { TourID } from '../types/tour' import { useAppAgent } from '../utils/agent' -import { - Fields, - evaluatePredicates, - getCredentialInfo, -} from '../utils/helpers' +import { Fields, evaluatePredicates, getCredentialInfo } from '../utils/helpers' import { testIdWithKey } from '../utils/testable' import ProofRequestAccept from './ProofRequestAccept' @@ -218,36 +212,36 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? { - ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes) - .map((key) => { - return { - [key]: retrievedCredentials.attributes[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates) - .map((key) => { - return { - [key]: retrievedCredentials.predicates[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), - } + ...retrievedCredentials, + attributes: Object.keys(retrievedCredentials.attributes) + .map((key) => { + return { + [key]: retrievedCredentials.attributes[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), + predicates: Object.keys(retrievedCredentials.predicates) + .map((key) => { + return { + [key]: retrievedCredentials.predicates[key].filter((attr) => + credList.includes(attr.credentialId) + ), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}), + } : undefined setRetrievedCredentials(selectRetrievedCredentials) @@ -480,7 +474,9 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const handleAltCredChange = (selectedCred: string, altCredentials: string[]) => { const onCredChange = (cred: string) => { - const newSelectedCreds = (selectedCredentials.length > 0 ? selectedCredentials : activeCreds.map(item => item.credId)).filter((id) => !altCredentials.includes(id)) + const newSelectedCreds = ( + selectedCredentials.length > 0 ? selectedCredentials : activeCreds.map((item) => item.credId) + ).filter((id) => !altCredentials.includes(id)) setSelectedCredentials([cred, ...newSelectedCreds]) } navigation.getParent()?.navigate(Stacks.ProofRequestsStack, { @@ -489,7 +485,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { selectedCred, altCredentials, proofId, - onCredChange + onCredChange, }, }) } @@ -552,8 +548,8 @@ const ProofRequest: React.FC = ({ navigation, route }) => { handleAltCredChange={ item.altCredentials && item.altCredentials.length > 1 ? () => { - handleAltCredChange(item.credId, item.altCredentials) - } + handleAltCredChange(item.credId, item.altCredentials) + } : undefined } proof={true} diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index a1347062ad..94cf26f221 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -104,7 +104,12 @@ export type ProofRequestsStackParams = { [Screens.ProofDetails]: { recordId: string; isHistory?: boolean; senderReview?: boolean } [Screens.ProofRequestDetails]: { templateId: string; connectionId?: string } [Screens.ProofRequestUsageHistory]: { templateId: string } - [Screens.ProofChangeCredential]: { selectedCred:string, altCredentials: string[]; proofId: string; onCredChange: (arg: string) => void } + [Screens.ProofChangeCredential]: { + selectedCred: string + altCredentials: string[] + proofId: string + onCredChange: (arg: string) => void + } } export type CredentialStackParams = { diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 8d1ee5cb1e..620bf9eab2 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -36,21 +36,19 @@ import { Buffer } from 'buffer' import moment from 'moment' import { ParsedUrl, parseUrl } from 'query-string' import { Dispatch, ReactNode, SetStateAction } from 'react' +import { TFunction } from 'react-i18next' +import { DeviceEventEmitter } from 'react-native' import { EventTypes, domain } from '../constants' import { i18n } from '../localization/index' import { Role } from '../types/chat' +import { BifoldError } from '../types/error' import { ChildFn } from '../types/tour' export { parsedCredDefNameFromCredential } from './cred-def' import { BifoldAgent } from './agent' import { parseCredDefFromId } from './cred-def' -import { BifoldError } from '../types/error' - -import { DeviceEventEmitter } from 'react-native' -import { TFunction } from 'react-i18next' - export { parsedCredDefName } from './cred-def' export { parsedSchema } from './schema' @@ -418,71 +416,6 @@ export const evaluatePredicates = }) } -export const retrieveCredentialsForProof = async ( - agent: BifoldAgent, - proof: ProofExchangeRecord, - fullCredentials: CredentialExchangeRecord[], - t: TFunction<'translation', undefined> -) => { - try { - const format = await agent.proofs.getFormatData(proof.id) - const hasAnonCreds = format.request?.anoncreds !== undefined - const hasIndy = format.request?.indy !== undefined - const credentials = await agent.proofs.getCredentialsForRequest({ - proofRecordId: proof.id, - proofFormats: { - // FIXME: AFJ will try to use the format, even if the value is undefined (but the key is present) - // We should ignore the key, if the value is undefined. For now this is a workaround. - ...(hasIndy - ? { - indy: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } - : {}), - - ...(hasAnonCreds - ? { - anoncreds: { - // Setting `filterByNonRevocationRequirements` to `false` returns all - // credentials even if they are revokable (and revoked). We need this to - // be able to show why a proof cannot be satisfied. Otherwise we can only - // show failure. - filterByNonRevocationRequirements: false, - }, - } - : {}), - }, - }) - if (!credentials) { - throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) - } - - if (!format) { - throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) - } - - if (!(format && credentials && fullCredentials)) { - return - } - - const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy - - const attributes = processProofAttributes(format.request, credentials, fullCredentials) - const predicates = processProofPredicates(format.request, credentials, fullCredentials) - - const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) - return { groupedProof: groupedProof, retrievedCredentials: proofFormat, fullCredentials } - } catch (err: unknown) { - const error = new BifoldError(t('Error.Title1043'), t('Error.Message1043'), (err as Error)?.message ?? err, 1043) - DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) - } -} - export const processProofAttributes = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, @@ -562,6 +495,26 @@ export const processProofAttributes = ( return processedAttributes } +export const mergeAttributesAndPredicates = ( + attributes: { [key: string]: ProofCredentialAttributes }, + predicates: { [key: string]: ProofCredentialPredicates } +) => { + const merged: { [key: string]: ProofCredentialAttributes & ProofCredentialPredicates } = { ...attributes } + for (const [key, predicate] of Object.entries(predicates)) { + const existingEntry = merged[key] + if (existingEntry) { + const mergedAltCreds = existingEntry.altCredentials?.filter((credId) => + predicate.altCredentials?.includes(credId) + ) + merged[key] = { ...existingEntry, ...predicate } + merged[key].altCredentials = mergedAltCreds + } else { + merged[key] = predicate + } + } + return merged +} + export const processProofPredicates = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, @@ -635,24 +588,69 @@ export const processProofPredicates = ( return processedPredicates } -export const mergeAttributesAndPredicates = ( - attributes: { [key: string]: ProofCredentialAttributes }, - predicates: { [key: string]: ProofCredentialPredicates } +export const retrieveCredentialsForProof = async ( + agent: BifoldAgent, + proof: ProofExchangeRecord, + fullCredentials: CredentialExchangeRecord[], + t: TFunction<'translation', undefined> ) => { - const merged: { [key: string]: ProofCredentialAttributes & ProofCredentialPredicates } = { ...attributes } - for (const [key, predicate] of Object.entries(predicates)) { - const existingEntry = merged[key] - if (existingEntry) { - const mergedAltCreds = existingEntry.altCredentials?.filter((credId) => - predicate.altCredentials?.includes(credId) - ) - merged[key] = { ...existingEntry, ...predicate } - merged[key].altCredentials = mergedAltCreds - } else { - merged[key] = predicate + try { + const format = await agent.proofs.getFormatData(proof.id) + const hasAnonCreds = format.request?.anoncreds !== undefined + const hasIndy = format.request?.indy !== undefined + const credentials = await agent.proofs.getCredentialsForRequest({ + proofRecordId: proof.id, + proofFormats: { + // FIXME: AFJ will try to use the format, even if the value is undefined (but the key is present) + // We should ignore the key, if the value is undefined. For now this is a workaround. + ...(hasIndy + ? { + indy: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } + : {}), + + ...(hasAnonCreds + ? { + anoncreds: { + // Setting `filterByNonRevocationRequirements` to `false` returns all + // credentials even if they are revokable (and revoked). We need this to + // be able to show why a proof cannot be satisfied. Otherwise we can only + // show failure. + filterByNonRevocationRequirements: false, + }, + } + : {}), + }, + }) + if (!credentials) { + throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) + } + + if (!format) { + throw new Error(t('ProofRequest.RequestedCredentialsCouldNotBeFound')) } + + if (!(format && credentials && fullCredentials)) { + return + } + + const proofFormat = credentials.proofFormats.anoncreds ?? credentials.proofFormats.indy + + const attributes = processProofAttributes(format.request, credentials, fullCredentials) + const predicates = processProofPredicates(format.request, credentials, fullCredentials) + + const groupedProof = Object.values(mergeAttributesAndPredicates(attributes, predicates)) + return { groupedProof: groupedProof, retrievedCredentials: proofFormat, fullCredentials } + } catch (err: unknown) { + const error = new BifoldError(t('Error.Title1043'), t('Error.Message1043'), (err as Error)?.message ?? err, 1043) + DeviceEventEmitter.emit(EventTypes.ERROR_ADDED, error) } - return merged } /** From 599aa2261e01ef00befc7c01920d09cb31ad0fa1 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Mon, 25 Sep 2023 15:16:32 -0700 Subject: [PATCH 05/15] updated tests Signed-off-by: wadeking98 --- .../App/components/misc/CredentialCard11.tsx | 3 +- .../legacy/core/App/localization/en/index.ts | 1 + .../legacy/core/App/localization/fr/index.ts | 1 + .../core/App/localization/pt-br/index.ts | 1 + .../legacy/core/App/screens/ProofRequest.tsx | 2 +- packages/legacy/core/App/utils/helpers.ts | 2 +- .../__tests__/screens/ProofRequest.test.tsx | 157 +++++++++++++++++- 7 files changed, 162 insertions(+), 5 deletions(-) diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index fc3d47b0a7..6e34c54c39 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -449,9 +449,10 @@ const CredentialCard11: React.FC = ({ - Change credential + {t('ProofRequest.ChangeCredential')} = ({ navigation, route }) => { }) .reduce((prev, current) => { return { ...prev, ...current } - }), + }, {}), predicates: Object.keys(retrievedCredentials.predicates) .map((key) => { return { diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 620bf9eab2..358c92ba0c 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -530,7 +530,7 @@ export const processProofPredicates = ( return {} } - for (const key of Object.keys(requestedProofPredicates)) { + for (const key of Object.keys(retrievedCredentialPredicates)) { const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])] .sort(credentialSortFn) .map((cred) => cred.credentialId) diff --git a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx index 6125242617..dc239ae11b 100644 --- a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx +++ b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx @@ -5,7 +5,7 @@ import { useAgent, useProofById } from '@aries-framework/react-hooks' import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock' import { useNavigation } from '@react-navigation/core' import '@testing-library/jest-native/extend-expect' -import { cleanup, render, waitFor } from '@testing-library/react-native' +import { cleanup, fireEvent, render, waitFor } from '@testing-library/react-native' import React from 'react' import { ConfigurationContext } from '../../App/contexts/configuration' @@ -149,7 +149,7 @@ describe('displays a proof request screen', () => { }, }, requested_predicates: { - additionalProp2: { + age: { name: 'age', p_type: '<=', p_value: 18, @@ -283,6 +283,159 @@ describe('displays a proof request screen', () => { expect(declineButton).not.toBeNull() }) + test('displays a proof request with multiple satisfying credentials', async () => { + const { agent } = useAgent() + const testEmail2 = 'test2@email.com' + const testTime2 = '2023-02-11 20:00:18.180718' + const testAge2 = '17' + + const { id: credentialId2 } = new CredentialExchangeRecord({ + threadId: '1', + state: CredentialState.Done, + credentialAttributes: [ + { + name: 'email', + value: testEmail, + toJSON: jest.fn(), + }, + { + name: 'time', + value: testTime, + toJSON: jest.fn(), + }, + { + name: 'age', + value: testAge, + toJSON: jest.fn(), + }, + ], + protocolVersion: 'v1', + }) + + const testRetrievedCredentials2 = { + proofFormats: { + indy: { + predicates: { + age: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId, + attributes: { age: testAge }, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId2, + attributes: { age: testAge2 }, + }, + }, + ], + }, + attributes: { + email: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId, + attributes: { email: testEmail }, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId2, + attributes: { email: testEmail2 }, + }, + }, + ], + time: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + attributes: { time: testTime }, + credentialId: credentialId, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + attributes: { time: testTime2 }, + credentialId: credentialId2, + }, + }, + ], + }, + }, + }, + } + + // @ts-ignore-next-line + agent?.proofs.getFormatData.mockResolvedValue(testProofFormatData) + + // @ts-ignore-next-line + agent?.proofs.getCredentialsForRequest.mockResolvedValue(testRetrievedCredentials2) + + const navigation = useNavigation() + + const { getByText, getByTestId, queryByText } = render( + + + + + + ) + + await waitFor(() => { + Promise.resolve() + }) + const changeCred = getByText('ProofRequest.ChangeCredential', { exact: false }) + const changeCredButton = getByTestId(testIdWithKey('changeCredential')) + const contact = getByText('ContactDetails.AContact', { exact: false }) + const missingInfo = queryByText('ProofRequest.IsRequestingSomethingYouDontHaveAvailable', { exact: false }) + const missingClaim = queryByText('ProofRequest.NotAvailableInYourWallet', { exact: false }) + const emailLabel = getByText(/Email/, { exact: false }) + const emailValue = getByText(testEmail) + const timeLabel = getByText(/Time/, { exact: false }) + const timeValue = getByText(testTime) + const shareButton = getByTestId(testIdWithKey('Share')) + const declineButton = getByTestId(testIdWithKey('Decline')) + + expect(changeCred).not.toBeNull() + expect(changeCredButton).not.toBeNull() + expect(contact).not.toBeNull() + expect(contact).toBeTruthy() + expect(missingInfo).toBeNull() + expect(emailLabel).not.toBeNull() + expect(emailLabel).toBeTruthy() + expect(emailValue).not.toBeNull() + expect(emailValue).toBeTruthy() + expect(timeLabel).not.toBeNull() + expect(timeLabel).toBeTruthy() + expect(timeValue).not.toBeNull() + expect(timeValue).toBeTruthy() + expect(missingClaim).toBeNull() + expect(shareButton).not.toBeNull() + expect(shareButton).toBeEnabled() + expect(declineButton).not.toBeNull() + + fireEvent(changeCredButton, 'press') + expect(navigation.navigate).toBeCalledTimes(1) + }) + test('displays a proof request with one or more claims not available', async () => { const { agent } = useAgent() From f7af96b290b57e319ffc81f832ab58db419cd802 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Mon, 25 Sep 2023 16:12:18 -0700 Subject: [PATCH 06/15] updated tests Signed-off-by: wadeking98 --- .../App/screens/ProofChangeCredential.tsx | 4 +- .../screens/ProofChangeCredential.test.tsx | 284 ++++++++++++++++++ .../__tests__/screens/ProofRequest.test.tsx | 6 +- 3 files changed, 290 insertions(+), 4 deletions(-) create mode 100644 packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index 5703350e61..81f071c8ee 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -14,6 +14,7 @@ import { getAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' import { ProofRequestsStackParams, Screens } from '../types/navigators' import { Fields, evaluatePredicates } from '../utils/helpers' +import { testIdWithKey } from '../utils/testable' type ProofChangeProps = StackScreenProps @@ -132,7 +133,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } const changeCred = (credId: string) => { onCredChange(credId) - navigation.getParent()?.goBack() + navigation.goBack() } const hasSatisfiedPredicates = (fields: Fields, credId?: string) => proofItems.flatMap((item) => evaluatePredicates(fields, credId)(item)).every((p) => p.satisfied) @@ -146,6 +147,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } return ( changeCred(item.credId ?? '', item.altCredentials)} style={[item.credId === selectedCred ? styles.selectedCred : undefined, { marginBottom: 10 }]} > diff --git a/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx b/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx new file mode 100644 index 0000000000..d70d593c8b --- /dev/null +++ b/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx @@ -0,0 +1,284 @@ +import { INDY_PROOF_REQUEST_ATTACHMENT_ID, V1RequestPresentationMessage } from '@aries-framework/anoncreds' +import { CredentialExchangeRecord, CredentialState, ProofExchangeRecord, ProofState } from '@aries-framework/core' +import { Attachment, AttachmentData } from '@aries-framework/core/build/decorators/attachment/Attachment' +import { useAgent, useProofById } from '@aries-framework/react-hooks' +import mockRNCNetInfo from '@react-native-community/netinfo/jest/netinfo-mock' +import { useNavigation } from '@react-navigation/core' +import '@testing-library/jest-native/extend-expect' +import { cleanup, fireEvent, render, waitFor } from '@testing-library/react-native' +import React from 'react' + +import { ConfigurationContext } from '../../App/contexts/configuration' +import { NetworkProvider } from '../../App/contexts/network' +import { testIdWithKey } from '../../App/utils/testable' +import configurationContext from '../contexts/configuration' +import timeTravel from '../helpers/timetravel' +import ProofChangeCredential from '../../App/screens/ProofChangeCredential' + +jest.mock('react-native/Libraries/EventEmitter/NativeEventEmitter') +jest.mock('@react-native-community/netinfo', () => mockRNCNetInfo) +jest.mock('react-native/Libraries/Animated/NativeAnimatedHelper') +jest.mock('@react-navigation/core', () => { + return require('../../__mocks__/custom/@react-navigation/core') +}) +jest.mock('@react-navigation/native', () => { + return require('../../__mocks__/custom/@react-navigation/native') +}) + +jest.mock('@hyperledger/anoncreds-react-native', () => ({})) +jest.mock('@hyperledger/aries-askar-react-native', () => ({})) +jest.mock('@hyperledger/indy-vdr-react-native', () => ({})) +jest.useFakeTimers('legacy') +jest.spyOn(global, 'setTimeout') + +describe('displays a credential selection screen', () => { + const testEmail = 'test@email.com' + const testTime = '2022-02-11 20:00:18.180718' + const testAge = '16' + const testEmail2 = 'test2@email.com' + const testTime2 = '2023-02-11 20:00:18.180718' + const testAge2 = '17' + + const { id: presentationMessageId } = new V1RequestPresentationMessage({ + comment: 'some comment', + requestAttachments: [ + new Attachment({ + id: INDY_PROOF_REQUEST_ATTACHMENT_ID, + mimeType: 'application/json', + data: new AttachmentData({ + json: { + name: 'test proof request', + version: '1.0.0', + nonce: '1', + requestedAttributes: { + email: { + name: 'email', + }, + time: { + name: 'time', + }, + }, + requestedPredicates: { + age: { + name: 'age', + pType: '<=', + pValue: 18, + }, + }, + }, + }), + }), + ], + }) + + const attributeBase = { + referent: '', + schemaId: '', + credentialDefinitionId: 'AAAAAAAAAAAAAAAAAAAAAA:1:AA:1234:test', + toJSON: jest.fn(), + } + + const testProofRequest = new ProofExchangeRecord({ + connectionId: '', + threadId: presentationMessageId, + state: ProofState.RequestReceived, + protocolVersion: 'V1', + }) + + const testProofFormatData = { + request: { + indy: { + requested_attributes: { + email: { + name: 'email', + restrictions: [ + { + cred_def_id: attributeBase.credentialDefinitionId, + }, + ], + }, + time: { + name: 'time', + restrictions: [ + { + cred_def_id: attributeBase.credentialDefinitionId, + }, + ], + }, + }, + requested_predicates: { + age: { + name: 'age', + p_type: '<=', + p_value: 18, + restrictions: [{ cred_def_id: attributeBase.credentialDefinitionId }], + }, + }, + }, + }, + } + + const { id: credentialId } = new CredentialExchangeRecord({ + threadId: '1', + state: CredentialState.Done, + credentialAttributes: [ + { + name: 'email', + value: testEmail, + toJSON: jest.fn(), + }, + { + name: 'time', + value: testTime, + toJSON: jest.fn(), + }, + { + name: 'age', + value: testAge, + toJSON: jest.fn(), + }, + ], + protocolVersion: 'v1', + }) + const { id: credentialId2 } = new CredentialExchangeRecord({ + threadId: '1', + state: CredentialState.Done, + credentialAttributes: [ + { + name: 'email', + value: testEmail2, + toJSON: jest.fn(), + }, + { + name: 'time', + value: testTime2, + toJSON: jest.fn(), + }, + { + name: 'age', + value: testAge2, + toJSON: jest.fn(), + }, + ], + protocolVersion: 'v1', + }) + + const testRetrievedCredentials2 = { + proofFormats: { + indy: { + predicates: { + age: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId, + attributes: { age: testAge }, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId2, + attributes: { age: testAge2 }, + }, + }, + ], + }, + attributes: { + email: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId, + attributes: { email: testEmail }, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + credentialId: credentialId2, + attributes: { email: testEmail2 }, + }, + }, + ], + time: [ + { + credentialId: credentialId, + revealed: true, + credentialInfo: { + ...attributeBase, + attributes: { time: testTime }, + credentialId: credentialId, + }, + }, + { + credentialId: credentialId2, + revealed: true, + credentialInfo: { + ...attributeBase, + attributes: { time: testTime2 }, + credentialId: credentialId2, + }, + }, + ], + }, + }, + }, + } + afterEach(() => { + cleanup() + }) + + beforeEach(() => { + jest.clearAllMocks() + }) + + describe('with multiple credentials', () => { + + beforeEach(() => { + jest.clearAllMocks() + + // @ts-ignore-next-line + useProofById.mockReturnValue(testProofRequest) + }) + + test('test credential selection', async () => { + const { agent } = useAgent() + + // @ts-ignore-next-line + agent?.proofs.getFormatData.mockResolvedValue(testProofFormatData) + + // @ts-ignore-next-line + agent?.proofs.getCredentialsForRequest.mockResolvedValue(testRetrievedCredentials2) + + const navigation = useNavigation() + + const onCredChange = jest.fn() + const tree = render( + + + + + + ) + + await waitFor(() => { + timeTravel(1000) + }) + + const firstCred = tree.getByTestId(testIdWithKey(`select:${credentialId}`)) + expect(firstCred).not.toBeNull() + fireEvent(firstCred, 'press') + expect(navigation.goBack).toBeCalledTimes(1) + expect(onCredChange).toBeCalledTimes(1) + }) + }) +}) \ No newline at end of file diff --git a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx index dc239ae11b..8a131023b9 100644 --- a/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx +++ b/packages/legacy/core/__tests__/screens/ProofRequest.test.tsx @@ -295,17 +295,17 @@ describe('displays a proof request screen', () => { credentialAttributes: [ { name: 'email', - value: testEmail, + value: testEmail2, toJSON: jest.fn(), }, { name: 'time', - value: testTime, + value: testTime2, toJSON: jest.fn(), }, { name: 'age', - value: testAge, + value: testAge2, toJSON: jest.fn(), }, ], From c72526212a75d1a700c2fdf34eac715d8dbfb773 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 26 Sep 2023 09:38:41 -0700 Subject: [PATCH 07/15] updated typedefs Signed-off-by: wadeking98 --- .../App/screens/ProofChangeCredential.tsx | 4 ++-- .../legacy/core/App/screens/ProofRequest.tsx | 15 ++++-------- packages/legacy/core/App/types/proof-items.ts | 24 +++++++++++++++++++ packages/legacy/core/App/utils/helpers.ts | 9 ++----- packages/oca/src/legacy/resolver/record.ts | 22 ----------------- 5 files changed, 33 insertions(+), 41 deletions(-) create mode 100644 packages/legacy/core/App/types/proof-items.ts diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index 81f071c8ee..744d212dee 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -1,5 +1,4 @@ import { AnonCredsCredentialsForProofRequest } from '@aries-framework/anoncreds' -import { ProofCredentialItems } from '@hyperledger/aries-oca/build/legacy' import { StackScreenProps } from '@react-navigation/stack' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -13,6 +12,7 @@ import { useTheme } from '../contexts/theme' import { getAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' import { ProofRequestsStackParams, Screens } from '../types/navigators' +import { ProofCredentialItems } from '../types/proof-items' import { Fields, evaluatePredicates } from '../utils/helpers' import { testIdWithKey } from '../utils/testable' @@ -148,7 +148,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } changeCred(item.credId ?? '', item.altCredentials)} + onPress={() => changeCred(item.credId ?? '')} style={[item.credId === selectedCred ? styles.selectedCred : undefined, { marginBottom: 10 }]} > = ({ navigation, route }) => { } else { // we only want one of each satisfying credential groupedProof.forEach((item) => { - const credId = item.altCredentials[0] - if (!credList.includes(credId)) { + const credId = item.altCredentials?.[0] + if (credId && !credList.includes(credId)) { credList.push(credId) } }) @@ -548,7 +543,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { handleAltCredChange={ item.altCredentials && item.altCredentials.length > 1 ? () => { - handleAltCredChange(item.credId, item.altCredentials) + handleAltCredChange(item.credId, item.altCredentials ?? [item.credId]) } : undefined } diff --git a/packages/legacy/core/App/types/proof-items.ts b/packages/legacy/core/App/types/proof-items.ts new file mode 100644 index 0000000000..99558263a3 --- /dev/null +++ b/packages/legacy/core/App/types/proof-items.ts @@ -0,0 +1,24 @@ +import { CredentialExchangeRecord } from '@aries-framework/core' +import { Attribute, Predicate } from '@hyperledger/aries-oca/build/legacy' + +export interface ProofCredentialAttributes { + altCredentials?: string[] + credExchangeRecord?: CredentialExchangeRecord + credId: string + credDefId?: string + schemaId?: string + credName: string + attributes?: Attribute[] +} + +export interface ProofCredentialPredicates { + altCredentials?: string[] + credExchangeRecord?: CredentialExchangeRecord + credId: string + credDefId?: string + schemaId?: string + credName: string + predicates?: Predicate[] +} + +export interface ProofCredentialItems extends ProofCredentialAttributes, ProofCredentialPredicates {} diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 358c92ba0c..b056444298 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -25,13 +25,7 @@ import { ProofFormatDataMessagePayload, } from '@aries-framework/core/build/modules/proofs/protocol/ProofProtocolOptions' import { useConnectionById } from '@aries-framework/react-hooks' -import { - Attribute, - Predicate, - ProofCredentialAttributes, - ProofCredentialItems, - ProofCredentialPredicates, -} from '@hyperledger/aries-oca/build/legacy' +import { Attribute, Predicate } from '@hyperledger/aries-oca/build/legacy' import { Buffer } from 'buffer' import moment from 'moment' import { ParsedUrl, parseUrl } from 'query-string' @@ -43,6 +37,7 @@ import { EventTypes, domain } from '../constants' import { i18n } from '../localization/index' import { Role } from '../types/chat' import { BifoldError } from '../types/error' +import { ProofCredentialAttributes, ProofCredentialItems, ProofCredentialPredicates } from '../types/proof-items' import { ChildFn } from '../types/tour' export { parsedCredDefNameFromCredential } from './cred-def' diff --git a/packages/oca/src/legacy/resolver/record.ts b/packages/oca/src/legacy/resolver/record.ts index 9c8f9eb5ff..0dba24724c 100644 --- a/packages/oca/src/legacy/resolver/record.ts +++ b/packages/oca/src/legacy/resolver/record.ts @@ -77,25 +77,3 @@ export class Predicate extends Field { this.satisfied = params.satisfied } } - -export interface ProofCredentialAttributes { - altCredentials?: string[] - credExchangeRecord?: CredentialExchangeRecord - credId: string - credDefId?: string - schemaId?: string - credName: string - attributes?: Attribute[] -} - -export interface ProofCredentialPredicates { - altCredentials?: string[] - credExchangeRecord?: CredentialExchangeRecord - credId: string - credDefId?: string - schemaId?: string - credName: string - predicates?: Predicate[] -} - -export interface ProofCredentialItems extends ProofCredentialAttributes, ProofCredentialPredicates {} From 2edea524b9317e920e70c57fddcf06bc68a83da0 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 26 Sep 2023 11:24:51 -0700 Subject: [PATCH 08/15] code factoring changes Signed-off-by: wadeking98 --- .../App/screens/ProofChangeCredential.tsx | 58 +++++------ .../legacy/core/App/screens/ProofRequest.tsx | 99 ++++++++++--------- 2 files changed, 80 insertions(+), 77 deletions(-) diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index 744d212dee..438bc5fd70 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -1,4 +1,8 @@ -import { AnonCredsCredentialsForProofRequest } from '@aries-framework/anoncreds' +import { + AnonCredsCredentialsForProofRequest, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicateMatch, +} from '@aries-framework/anoncreds' import { StackScreenProps } from '@react-navigation/stack' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' @@ -69,37 +73,33 @@ const ProofChangeCredential: React.FC = ({ route, navigation } setLoading(false) const activeCreds = groupedProof.filter((proof) => altCredentials.includes(proof.credId)) const credList = activeCreds.map((cred) => cred.credId) + const formatCredentials = ( + retrievedItems: Record + ) => { + return Object.keys(retrievedItems) + .map((key) => { + return { + [key]: retrievedItems[key].filter((attr) => credList.includes(attr.credentialId)), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}) + } const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? { ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes) - .map((key) => { - return { - [key]: retrievedCredentials.attributes[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates) - .map((key) => { - return { - [key]: retrievedCredentials.predicates[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), + attributes: formatCredentials(retrievedCredentials.attributes) as Record< + string, + AnonCredsRequestedAttributeMatch[] + >, + predicates: formatCredentials(retrievedCredentials.predicates) as Record< + string, + AnonCredsRequestedPredicateMatch[] + >, } : undefined setRetrievedCredentials(selectRetrievedCredentials) diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index 8d5813abbb..ba77ff645a 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -1,6 +1,10 @@ import type { StackScreenProps } from '@react-navigation/stack' -import { AnonCredsCredentialsForProofRequest } from '@aries-framework/anoncreds' +import { + AnonCredsCredentialsForProofRequest, + AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicateMatch, +} from '@aries-framework/anoncreds' import { CredentialExchangeRecord } from '@aries-framework/core' import { useConnectionById, useProofById } from '@aries-framework/react-hooks' import { Attribute, Predicate } from '@hyperledger/aries-oca/build/legacy' @@ -205,37 +209,35 @@ const ProofRequest: React.FC = ({ navigation, route }) => { }) } + const formatCredentials = ( + retrievedItems: Record, + credList: string[] + ) => { + return Object.keys(retrievedItems) + .map((key) => { + return { + [key]: retrievedItems[key].filter((attr) => credList.includes(attr.credentialId)), + } + }) + .reduce((prev, curr) => { + return { + ...prev, + ...curr, + } + }, {}) + } + const selectRetrievedCredentials: AnonCredsCredentialsForProofRequest | undefined = retrievedCredentials ? { ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes) - .map((key) => { - return { - [key]: retrievedCredentials.attributes[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates) - .map((key) => { - return { - [key]: retrievedCredentials.predicates[key].filter((attr) => - credList.includes(attr.credentialId) - ), - } - }) - .reduce((prev, curr) => { - return { - ...prev, - ...curr, - } - }, {}), + attributes: formatCredentials(retrievedCredentials.attributes, credList) as Record< + string, + AnonCredsRequestedAttributeMatch[] + >, + predicates: formatCredentials(retrievedCredentials.predicates, credList) as Record< + string, + AnonCredsRequestedPredicateMatch[] + >, } : undefined setRetrievedCredentials(selectRetrievedCredentials) @@ -321,31 +323,32 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const formatToUse = format.request?.anoncreds ? 'anoncreds' : 'indy' - // this is the best way to supply our desired credentials in the proof, otherwise it selects them automatically - const credObject = { - ...retrievedCredentials, - attributes: Object.keys(retrievedCredentials.attributes) - .map((key) => { - return { - [key]: retrievedCredentials.attributes[key].find((cred) => - activeCreds.map((item) => item.credId).includes(cred.credentialId) - ), - } - }) - .reduce((prev, current) => { - return { ...prev, ...current } - }, {}), - predicates: Object.keys(retrievedCredentials.predicates) + const formatCredentials = ( + retrievedItems: Record, + credList: string[] + ) => { + return Object.keys(retrievedItems) .map((key) => { return { - [key]: retrievedCredentials.predicates[key].find((cred) => - activeCreds.map((item) => item.credId).includes(cred.credentialId) - ), + [key]: retrievedItems[key].find((cred) => credList.includes(cred.credentialId)), } }) .reduce((prev, current) => { return { ...prev, ...current } - }, {}), + }, {}) + } + + // this is the best way to supply our desired credentials in the proof, otherwise it selects them automatically + const credObject = { + ...retrievedCredentials, + attributes: formatCredentials( + retrievedCredentials.attributes, + activeCreds.map((item) => item.credId) + ), + predicates: formatCredentials( + retrievedCredentials.predicates, + activeCreds.map((item) => item.credId) + ), selfAttestedAttributes: {}, } const automaticRequestedCreds = { proofFormats: { [formatToUse]: { ...credObject } } } From 251a17ee650ab0c1880f73d92add11ec35710d12 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 26 Sep 2023 11:29:34 -0700 Subject: [PATCH 09/15] remove unused selectedCreds from proof screen Signed-off-by: wadeking98 --- packages/legacy/core/App/types/navigators.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/legacy/core/App/types/navigators.ts b/packages/legacy/core/App/types/navigators.ts index 94cf26f221..596cc94c95 100644 --- a/packages/legacy/core/App/types/navigators.ts +++ b/packages/legacy/core/App/types/navigators.ts @@ -142,7 +142,7 @@ export type SettingStackParams = { export type NotificationStackParams = { [Screens.CredentialDetails]: { credentialId: string } [Screens.CredentialOffer]: { credentialId: string } - [Screens.ProofRequest]: { proofId: string; selectedCredentials?: string[] } + [Screens.ProofRequest]: { proofId: string } [Screens.CustomNotification]: undefined [Screens.ProofDetails]: { recordId: string } } From 8e10b2b5fd782ba8553daa46394578fa85fd0c8e Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 26 Sep 2023 11:30:56 -0700 Subject: [PATCH 10/15] updated PB lang index Signed-off-by: wadeking98 --- packages/legacy/core/App/localization/en/index.ts | 2 +- packages/legacy/core/App/localization/fr/index.ts | 2 +- packages/legacy/core/App/localization/pt-br/index.ts | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/legacy/core/App/localization/en/index.ts b/packages/legacy/core/App/localization/en/index.ts index 9e64c9b026..63a0e2944f 100644 --- a/packages/legacy/core/App/localization/en/index.ts +++ b/packages/legacy/core/App/localization/en/index.ts @@ -417,7 +417,7 @@ const translation = { "ChangeCredential": "Change credential", "RejectThisProof?": "Reject this Proof Request?", "DeclineThisProof?": "Decline this Proof Request?", - "MultipleCredentials": "You have multiple crdentials to choose from:", + "MultipleCredentials": "You have multiple credentials to choose from:", "AcceptingProof": "Accepting Proof", "SuccessfullyAcceptedProof": "Successfully Accepted Proof", "SensitiveInformation": "This request is asking for sensitive information.", diff --git a/packages/legacy/core/App/localization/fr/index.ts b/packages/legacy/core/App/localization/fr/index.ts index 1d8f8dd415..985068fc9f 100644 --- a/packages/legacy/core/App/localization/fr/index.ts +++ b/packages/legacy/core/App/localization/fr/index.ts @@ -415,7 +415,7 @@ const translation = { "ChangeCredential": "Change credential (FR)", "RejectThisProof?": "Rejeter cette preuve?", "AcceptingProof": "Acceptation de la preuve", - "MultipleCredentials": "You have multiple crdentials to choose from: (FR)", + "MultipleCredentials": "You have multiple credentials to choose from: (FR)", "SuccessfullyAcceptedProof": "Preuve acceptée avec succès", "SensitiveInformation": "This request is asking for sensitive information. (FR)", "RejectingProof": "Rejet de la preuve", diff --git a/packages/legacy/core/App/localization/pt-br/index.ts b/packages/legacy/core/App/localization/pt-br/index.ts index af36129b73..62a910b6d2 100644 --- a/packages/legacy/core/App/localization/pt-br/index.ts +++ b/packages/legacy/core/App/localization/pt-br/index.ts @@ -394,11 +394,11 @@ const translation = { "ProofRequest": "Requisição de Prova", "RequestProcessing": "Só um momento...", "OfferDelay": "Atrasar oferta", - "ChangeCredential": "Change credential (PB)", + "ChangeCredential": "Escolher credencial", "RejectThisProof?": "Rejeitar esta Requisição de Prova?", "DeclineThisProof?": "Recusar esta Requisição de Prova?", "AcceptingProof": "Aceitando Prova", - "MultipleCredentials": "You have multiple crdentials to choose from: (PB)", + "MultipleCredentials": "Você tem múltiplas credenciais para escolher:", "SuccessfullyAcceptedProof": "Prova Aceita com Sucesso", "SensitiveInformation": "This request is asking for sensitive information. (PB)", "ProofRequestNotFound": "Requisição de Prova não encontrada.", @@ -484,7 +484,7 @@ const translation = { "Notifications": "Notificações", "CredentialOffer": "Oferta de Credencial", "ProofRequest": "Requisição de Prova", - "ProofChangeCredential":"Choose a credential (PB)", + "ProofChangeCredential":"Escolha uma credencial", "ProofRequestDetails": "Detalhes Da Solicitação De Comprovação", "ProofRequestAttributeDetails": "Atributos de Requisição de Prova", "ProofDetails": "Detalhes da prova", From 9a7b448c9ae7d971a33eefc2ff4c49ed5003316b Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 26 Sep 2023 14:00:28 -0700 Subject: [PATCH 11/15] switch to pressable Signed-off-by: wadeking98 --- packages/legacy/core/App/screens/ProofChangeCredential.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index 438bc5fd70..83f0c55552 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -6,7 +6,7 @@ import { import { StackScreenProps } from '@react-navigation/stack' import React, { useEffect, useState } from 'react' import { useTranslation } from 'react-i18next' -import { DeviceEventEmitter, FlatList, StyleSheet, Text, TouchableOpacity, View } from 'react-native' +import { DeviceEventEmitter, FlatList, Pressable, StyleSheet, Text, View } from 'react-native' import { SafeAreaView } from 'react-native-safe-area-context' import RecordLoading from '../components/animated/RecordLoading' @@ -146,7 +146,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } renderItem={({ item }) => { return ( - changeCred(item.credId ?? '')} style={[item.credId === selectedCred ? styles.selectedCred : undefined, { marginBottom: 10 }]} @@ -164,7 +164,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } satisfiedPredicates={hasSatisfiedPredicates(getCredentialsFields(), item.credId)} proof={true} > - + ) }} From fab2a2544712f7257f60fd5059afb7e140dc550d Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Wed, 27 Sep 2023 14:57:01 -0700 Subject: [PATCH 12/15] fixed ui bug on cred card Signed-off-by: wadeking98 --- .../App/components/misc/CredentialCard11.tsx | 5 +- packages/legacy/core/App/hooks/proofs.ts | 2 +- .../App/screens/ProofChangeCredential.tsx | 4 +- .../legacy/core/App/screens/ProofRequest.tsx | 4 +- .../CredentialOffer.test.tsx.snap | 75 ++++++++++++------- 5 files changed, 57 insertions(+), 33 deletions(-) diff --git a/packages/legacy/core/App/components/misc/CredentialCard11.tsx b/packages/legacy/core/App/components/misc/CredentialCard11.tsx index f266589dd5..83dea9a5d0 100644 --- a/packages/legacy/core/App/components/misc/CredentialCard11.tsx +++ b/packages/legacy/core/App/components/misc/CredentialCard11.tsx @@ -546,7 +546,10 @@ const CredentialCard11: React.FC = ({ } return ( - + ) diff --git a/packages/legacy/core/App/hooks/proofs.ts b/packages/legacy/core/App/hooks/proofs.ts index 1d8acb3ea8..dedcac47ed 100644 --- a/packages/legacy/core/App/hooks/proofs.ts +++ b/packages/legacy/core/App/hooks/proofs.ts @@ -13,7 +13,7 @@ export const useProofsByConnectionId = (connectionId: string): ProofExchangeReco ) } -export const getAllCredentialsForProof = (proofId: string) => { +export const useAllCredentialsForProof = (proofId: string) => { const { t } = useTranslation() const { agent } = useAgent() const fullCredentials = useCredentials().records diff --git a/packages/legacy/core/App/screens/ProofChangeCredential.tsx b/packages/legacy/core/App/screens/ProofChangeCredential.tsx index 83f0c55552..43942d4084 100644 --- a/packages/legacy/core/App/screens/ProofChangeCredential.tsx +++ b/packages/legacy/core/App/screens/ProofChangeCredential.tsx @@ -13,7 +13,7 @@ import RecordLoading from '../components/animated/RecordLoading' import { CredentialCard } from '../components/misc' import { EventTypes } from '../constants' import { useTheme } from '../contexts/theme' -import { getAllCredentialsForProof } from '../hooks/proofs' +import { useAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' import { ProofRequestsStackParams, Screens } from '../types/navigators' import { ProofCredentialItems } from '../types/proof-items' @@ -35,7 +35,7 @@ const ProofChangeCredential: React.FC = ({ route, navigation } const [loading, setLoading] = useState(false) const [proofItems, setProofItems] = useState([]) const [retrievedCredentials, setRetrievedCredentials] = useState() - const credProofPromise = getAllCredentialsForProof(proofId) + const credProofPromise = useAllCredentialsForProof(proofId) const styles = StyleSheet.create({ pageContainer: { flex: 1, diff --git a/packages/legacy/core/App/screens/ProofRequest.tsx b/packages/legacy/core/App/screens/ProofRequest.tsx index ba77ff645a..79fe181656 100644 --- a/packages/legacy/core/App/screens/ProofRequest.tsx +++ b/packages/legacy/core/App/screens/ProofRequest.tsx @@ -30,7 +30,7 @@ import { useStore } from '../contexts/store' import { useTheme } from '../contexts/theme' import { useTour } from '../contexts/tour/tour-context' import { useOutOfBandByConnectionId } from '../hooks/connections' -import { getAllCredentialsForProof } from '../hooks/proofs' +import { useAllCredentialsForProof } from '../hooks/proofs' import { BifoldError } from '../types/error' import { NotificationStackParams, Screens, Stacks, TabStacks } from '../types/navigators' import { ProofCredentialAttributes, ProofCredentialItems, ProofCredentialPredicates } from '../types/proof-items' @@ -69,7 +69,7 @@ const ProofRequest: React.FC = ({ navigation, route }) => { const [activeCreds, setActiveCreds] = useState([]) const [selectedCredentials, setSelectedCredentials] = useState([]) const [store, dispatch] = useStore() - const credProofPromise = getAllCredentialsForProof(proofId) + const credProofPromise = useAllCredentialsForProof(proofId) const { start } = useTour() const screenIsFocused = useIsFocused() diff --git a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap index e4322de339..d0172c3e45 100644 --- a/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap +++ b/packages/legacy/core/__tests__/screens/__snapshots__/CredentialOffer.test.tsx.snap @@ -545,15 +545,22 @@ exports[`displays a credential offer screen accepting a credential 1`] = ` @@ -2416,15 +2423,22 @@ exports[`displays a credential offer screen declining a credential 1`] = ` @@ -4289,15 +4303,22 @@ exports[`displays a credential offer screen renders correctly 1`] = ` From 8d704ae15317d94a85a95ff9aa3094f1e8965581 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Fri, 29 Sep 2023 14:02:15 -0700 Subject: [PATCH 13/15] support for rev interval in other parts of proof Signed-off-by: wadeking98 --- packages/legacy/core/App/utils/helpers.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index b056444298..a05abc2879 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -422,6 +422,9 @@ export const processProofAttributes = ( const retrievedCredentialAttributes = credentials?.proofFormats?.indy?.attributes ?? credentials?.proofFormats?.anoncreds?.attributes + // non_revoked interval can sometimes be top level + const requestNonRevoked = request?.indy?.non_revoked ?? request?.anoncreds?.non_revoked + if (!requestedProofAttributes || !retrievedCredentialAttributes) { return {} } @@ -454,7 +457,6 @@ export const processProofAttributes = ( } else { continue } - const { name, names, non_revoked } = requestedProofAttributes[key] for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { @@ -481,7 +483,7 @@ export const processProofAttributes = ( credentialId: credential.credentialId, name: attributeName, value: attributeValue, - nonRevoked: non_revoked, + nonRevoked: requestNonRevoked ?? non_revoked, }) ) } @@ -525,6 +527,9 @@ export const processProofPredicates = ( return {} } + // non_revoked interval can sometimes be top level + const requestNonRevoked = request?.indy?.non_revoked ?? request?.anoncreds?.non_revoked + for (const key of Object.keys(retrievedCredentialPredicates)) { const altCredentials = [...(retrievedCredentialPredicates[key] ?? [])] .sort(credentialSortFn) @@ -575,7 +580,7 @@ export const processProofPredicates = ( revoked, pValue, pType, - nonRevoked: non_revoked, + nonRevoked: requestNonRevoked ?? non_revoked, }) ) } From 657b24ae24a35440e4c37d0963dea6ae1a196a32 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Fri, 29 Sep 2023 14:46:11 -0700 Subject: [PATCH 14/15] updated after react upgrade Signed-off-by: wadeking98 --- packages/legacy/core/App/utils/helpers.ts | 4 ++-- .../core/__tests__/screens/ProofChangeCredential.test.tsx | 2 +- packages/oca/src/legacy/resolver/record.ts | 1 - packages/react-native-attestation/package.json | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index de86f8a94e..84f106f9f4 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -395,7 +395,7 @@ export const evaluatePredicates = const credentialAttributes = getCredentialInfo(proofCredentialItems.credId, fields).map((ci) => ci.attributes) - return predicates.map((predicate) => { + return predicates.map((predicate: Predicate) => { const { pType: pType, pValue: pValue, name: field } = predicate let satisfied = false @@ -500,7 +500,7 @@ export const mergeAttributesAndPredicates = ( for (const [key, predicate] of Object.entries(predicates)) { const existingEntry = merged[key] if (existingEntry) { - const mergedAltCreds = existingEntry.altCredentials?.filter((credId) => + const mergedAltCreds = existingEntry.altCredentials?.filter((credId: string) => predicate.altCredentials?.includes(credId) ) merged[key] = { ...existingEntry, ...predicate } diff --git a/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx b/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx index d70d593c8b..7003954b7d 100644 --- a/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx +++ b/packages/legacy/core/__tests__/screens/ProofChangeCredential.test.tsx @@ -28,7 +28,7 @@ jest.mock('@react-navigation/native', () => { jest.mock('@hyperledger/anoncreds-react-native', () => ({})) jest.mock('@hyperledger/aries-askar-react-native', () => ({})) jest.mock('@hyperledger/indy-vdr-react-native', () => ({})) -jest.useFakeTimers('legacy') +jest.useFakeTimers({ legacyFakeTimers: true }) jest.spyOn(global, 'setTimeout') describe('displays a credential selection screen', () => { diff --git a/packages/oca/src/legacy/resolver/record.ts b/packages/oca/src/legacy/resolver/record.ts index 0dba24724c..39a7a7b93c 100644 --- a/packages/oca/src/legacy/resolver/record.ts +++ b/packages/oca/src/legacy/resolver/record.ts @@ -1,5 +1,4 @@ import { AnonCredsNonRevokedInterval, AnonCredsProofRequestRestriction } from '@aries-framework/anoncreds' -import { CredentialExchangeRecord } from '@aries-framework/core' export interface FieldParams { name: string | null diff --git a/packages/react-native-attestation/package.json b/packages/react-native-attestation/package.json index b7d37b4418..35e2f7c2b4 100644 --- a/packages/react-native-attestation/package.json +++ b/packages/react-native-attestation/package.json @@ -168,4 +168,4 @@ "type": "modules", "jsSrcsDir": "src" } -} \ No newline at end of file +} From 50cbbc5a991910a8edef07c7fd3f2b259a61d426 Mon Sep 17 00:00:00 2001 From: wadeking98 Date: Tue, 3 Oct 2023 13:02:51 -0700 Subject: [PATCH 15/15] fixed missing credential display Signed-off-by: wadeking98 --- packages/legacy/core/App/utils/helpers.ts | 79 +++++++++++++++++++++-- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/packages/legacy/core/App/utils/helpers.ts b/packages/legacy/core/App/utils/helpers.ts index 84f106f9f4..5968cb31b2 100644 --- a/packages/legacy/core/App/utils/helpers.ts +++ b/packages/legacy/core/App/utils/helpers.ts @@ -5,7 +5,9 @@ import { AnonCredsProofFormat, AnonCredsProofFormatService, AnonCredsProofRequestRestriction, + AnonCredsRequestedAttribute, AnonCredsRequestedAttributeMatch, + AnonCredsRequestedPredicate, AnonCredsRequestedPredicateMatch, LegacyIndyProofFormat, LegacyIndyProofFormatService, @@ -411,6 +413,31 @@ export const evaluatePredicates = }) } +const addMissingDisplayAttributes = (attrReq: AnonCredsRequestedAttribute) => { + const credName = credNameFromRestriction(attrReq.restrictions) + //there is no credId in this context so use credName as a placeholder + const processedAttributes: ProofCredentialAttributes = { + credExchangeRecord: undefined, + altCredentials: [credName], + credId: credName, + schemaId: undefined, + credDefId: undefined, + credName: credName, + attributes: [] as Attribute[], + } + const { name, names } = attrReq + for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { + processedAttributes.attributes?.push( + new Attribute({ + revoked: false, + credentialId: credName, + name: attributeName, + value: '', + }) + ) + } + return processedAttributes +} export const processProofAttributes = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, @@ -428,7 +455,6 @@ export const processProofAttributes = ( if (!requestedProofAttributes || !retrievedCredentialAttributes) { return {} } - for (const key of Object.keys(retrievedCredentialAttributes)) { const altCredentials = [...(retrievedCredentialAttributes[key] ?? [])] .sort(credentialSortFn) @@ -436,11 +462,20 @@ export const processProofAttributes = ( const credentialList = [...(retrievedCredentialAttributes[key] ?? [])].sort(credentialSortFn) + const { name, names, non_revoked } = requestedProofAttributes[key] + + if (credentialList.length <= 0) { + const missingAttributes = addMissingDisplayAttributes(requestedProofAttributes[key]) + if (!processedAttributes[key]) { + processedAttributes[key] = missingAttributes + } else { + processedAttributes[key].attributes?.push(...(missingAttributes.attributes ?? [])) + } + } + //iterate over all credentials that satisfy the proof for (const credential of credentialList) { - const credNameRestriction = credNameFromRestriction(requestedProofAttributes[key]?.restrictions) - - let credName = credNameRestriction ?? key + let credName = key if (credential?.credentialInfo?.credentialDefinitionId || credential?.credentialInfo?.schemaId) { credName = parseCredDefFromId( credential?.credentialInfo?.credentialDefinitionId, @@ -457,7 +492,6 @@ export const processProofAttributes = ( } else { continue } - const { name, names, non_revoked } = requestedProofAttributes[key] for (const attributeName of [...(names ?? (name && [name]) ?? [])]) { if (!processedAttributes[credential?.credentialId]) { @@ -512,6 +546,31 @@ export const mergeAttributesAndPredicates = ( return merged } +const addMissingDisplayPredicates = (predReq: AnonCredsRequestedPredicate) => { + const credName = credNameFromRestriction(predReq.restrictions) + //there is no credId in this context so use credName as a placeholder + const processedPredicates: ProofCredentialPredicates = { + credExchangeRecord: undefined, + altCredentials: [credName], + credId: credName, + schemaId: undefined, + credDefId: undefined, + credName: credName, + predicates: [] as Predicate[], + } + const { name, p_type: pType, p_value: pValue } = predReq + + processedPredicates.predicates?.push( + new Predicate({ + revoked: false, + credentialId: credName, + name: name, + pValue, + pType, + }) + ) + return processedPredicates +} export const processProofPredicates = ( request?: ProofFormatDataMessagePayload<[LegacyIndyProofFormat, AnonCredsProofFormat], 'request'> | undefined, credentials?: GetCredentialsForRequestReturn<[LegacyIndyProofFormatService, AnonCredsProofFormatService]>, @@ -536,6 +595,15 @@ export const processProofPredicates = ( .map((cred) => cred.credentialId) const credentialList = [...(retrievedCredentialPredicates[key] ?? [])].sort(credentialSortFn) + const { name, p_type: pType, p_value: pValue, non_revoked } = requestedProofPredicates[key] + if (credentialList.length <= 0) { + const missingPredicates = addMissingDisplayPredicates(requestedProofPredicates[key]) + if (!processedPredicates[key]) { + processedPredicates[key] = missingPredicates + } else { + processedPredicates[key].predicates?.push(...(missingPredicates.predicates ?? [])) + } + } for (const credential of credentialList) { let revoked = false @@ -549,7 +617,6 @@ export const processProofPredicates = ( continue } const { credentialDefinitionId, schemaId } = { ...credential, ...credential?.credentialInfo } - const { name, p_type: pType, p_value: pValue, non_revoked } = requestedProofPredicates[key] const credNameRestriction = credNameFromRestriction(requestedProofPredicates[key]?.restrictions)