Skip to content

Commit

Permalink
feat: credential selection in proof request (#113)
Browse files Browse the repository at this point in the history
Signed-off-by: Timo Glastra <[email protected]>
  • Loading branch information
TimoGlastra authored Jul 13, 2024
1 parent e62a97a commit 09e6459
Show file tree
Hide file tree
Showing 16 changed files with 653 additions and 358 deletions.
3 changes: 2 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
{
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "biomejs.biome"
}
105 changes: 54 additions & 51 deletions apps/funke/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { DefaultTheme, ThemeProvider } from '@react-navigation/native'
import { Stack } from 'expo-router'
import * as SplashScreen from 'expo-splash-screen'
import { useEffect, useState } from 'react'
import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { useSafeAreaInsets } from 'react-native-safe-area-context'

import { initializeAppAgent } from '.'
Expand Down Expand Up @@ -102,57 +103,59 @@ export default function HomeLayout() {

return (
<Provider>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
</GestureHandlerRootView>
</Provider>
)
}
2 changes: 2 additions & 0 deletions apps/funke/babel.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ module.exports = (api) => {
disableExtraction: process.env.NODE_ENV === 'development',
},
],
// used for bottom sheet
'react-native-reanimated/plugin',
],
}
}
2 changes: 2 additions & 0 deletions apps/funke/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"prebuild": "APP_VARIANT=development expo prebuild --no-install"
},
"dependencies": {
"@gorhom/bottom-sheet": "^4.6.3",
"@hyperledger/anoncreds-react-native": "^0.2.2",
"@hyperledger/aries-askar-react-native": "^0.2.0",
"@hyperledger/indy-vdr-react-native": "^0.2.0",
Expand Down Expand Up @@ -43,6 +44,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.2",
"react-native-get-random-values": "~1.11.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "15.2.0"
Expand Down
110 changes: 58 additions & 52 deletions apps/paradym/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { useEffect, useState } from 'react'
import { useSafeAreaInsets } from 'react-native-safe-area-context'
import { initializeAppAgent } from '.'

import { GestureHandlerRootView } from 'react-native-gesture-handler'
import { mediatorDid } from './constants'

void SplashScreen.preventAutoHideAsync()
Expand Down Expand Up @@ -143,58 +144,63 @@ export default function HomeLayout() {

return (
<Provider>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen options={{ presentation: 'modal', ...headerModalOptions }} name="notifications/didcomm" />
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AgentProvider agent={agent}>
<ThemeProvider value={DefaultTheme}>
<NoInternetToastProvider>
<DeeplinkHandler>
<Stack initialRouteName="index" screenOptions={{ headerShown: false }}>
<Stack.Screen
options={{
presentation: 'modal',
// Extra modal options not needed for QR Scanner
}}
name="(home)/scan"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdCredential"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/openIdPresentation"
/>
<Stack.Screen
options={{ presentation: 'modal', ...headerModalOptions }}
name="notifications/didcomm"
/>
<Stack.Screen
options={{
headerShown: true,
headerStyle: {
backgroundColor: config.tokens.color['grey-200'].val,
},
headerShadowVisible: false,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: 'Inbox',
headerTitleAlign: 'center',
headerTitleStyle: {
fontWeight: isAndroid() ? '700' : '500', // Match font weight on android to native back button style
fontSize: 18,
},
}}
name="notifications/inbox"
/>
<Stack.Screen
options={{
headerShown: true,
headerTransparent: true,
headerTintColor: config.tokens.color['primary-500'].val,
headerTitle: '',
}}
name="credentials/[id]"
/>
</Stack>
</DeeplinkHandler>
</NoInternetToastProvider>
</ThemeProvider>
</AgentProvider>
</GestureHandlerRootView>
</Provider>
)
}
1 change: 1 addition & 0 deletions apps/paradym/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"react-native-fs": "^2.20.0",
"react-native-gesture-handler": "~2.16.2",
"react-native-get-random-values": "~1.11.0",
"react-native-reanimated": "~3.10.1",
"react-native-safe-area-context": "4.10.1",
"react-native-screens": "~3.31.1",
"react-native-svg": "15.2.0"
Expand Down
5 changes: 5 additions & 0 deletions packages/agent/src/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,11 @@ function getW3cCredentialDisplay(
}
}

// Use background color from the JFF credential if not provided by the OID4VCI metadata
if (!credentialDisplay.backgroundColor && jffCredential.credentialBranding?.backgroundColor) {
credentialDisplay.backgroundColor = jffCredential.credentialBranding.backgroundColor
}

return {
...credentialDisplay,
// Last fallback, if there's really no name for the credential, we use a generic name
Expand Down
70 changes: 36 additions & 34 deletions packages/agent/src/format/formatPresentation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,51 +12,53 @@ export interface FormattedSubmission {
}

export interface FormattedSubmissionEntry {
name: string
/** can be either AnonCreds groupName or PEX inputDescriptorId */
inputDescriptorId: string
isSatisfied: boolean
credentialName: string
issuerName?: string

name: string
description?: string
requestedAttributes?: string[]
backgroundColor?: string

credentials: Array<{
id: string
credentialName: string
issuerName?: string
requestedAttributes?: string[]
backgroundColor?: string
}>
}

export function formatDifPexCredentialsForRequest(
credentialsForRequest: DifPexCredentialsForRequest
): FormattedSubmission {
const entries = credentialsForRequest.requirements.flatMap((requirement) => {
return requirement.submissionEntry.map((submission) => {
// FIXME: support credential selection from JFF branch
const [firstVerifiableCredential] = submission.verifiableCredentials
if (firstVerifiableCredential) {
// Credential can be satisfied
const { display, credential } = getCredentialForDisplay(firstVerifiableCredential.credentialRecord)

// TODO: support nesting
let requestedAttributes: string[]
if (firstVerifiableCredential.type === ClaimFormat.SdJwtVc) {
const { metadata, visibleProperties } = filterAndMapSdJwtKeys(firstVerifiableCredential.disclosedPayload)
requestedAttributes = [...Object.keys(visibleProperties), ...Object.keys(metadata)]
} else {
requestedAttributes = Object.keys(credential?.credentialSubject ?? {})
}

return {
name: submission.name ?? 'Unknown',
description: submission.purpose,
isSatisfied: true,
credentialName: display.name,
issuerName: display.issuer.name,
requestedAttributes,
backgroundColor: display.backgroundColor,
}
}
return requirement.submissionEntry.map((submission): FormattedSubmissionEntry => {
return {
inputDescriptorId: submission.inputDescriptorId,
name: submission.name ?? 'Unknown',
description: submission.purpose,
isSatisfied: false,
// fallback to submission name because there is no credential
credentialName: submission.name ?? 'Credential name',
isSatisfied: submission.verifiableCredentials.length >= 1,

credentials: submission.verifiableCredentials.map((verifiableCredential) => {
const { display, credential } = getCredentialForDisplay(verifiableCredential.credentialRecord)

// TODO: support nesting
let requestedAttributes: string[]
if (verifiableCredential.type === ClaimFormat.SdJwtVc) {
const { metadata, visibleProperties } = filterAndMapSdJwtKeys(verifiableCredential.disclosedPayload)
requestedAttributes = [...Object.keys(visibleProperties), ...Object.keys(metadata)]
} else {
requestedAttributes = Object.keys(credential?.credentialSubject ?? {})
}

return {
id: verifiableCredential.credentialRecord.id,
credentialName: display.name,
issuerName: display.issuer.name,
requestedAttributes,
backgroundColor: display.backgroundColor,
}
}),
}
})
})
Expand Down
Loading

0 comments on commit 09e6459

Please sign in to comment.